mirror of https://github.com/AMT-Cheif/drift.git
Start with some refactoring in the generator
The idea is that we have a setup that let's us use multiple backends (build, analyzer plugin, standalone) with maximum code sharing.
This commit is contained in:
parent
3cb00a4b31
commit
23fca61961
|
@ -8,7 +8,6 @@
|
||||||
# Files and directories created by pub
|
# Files and directories created by pub
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.packages
|
.packages
|
||||||
build/
|
|
||||||
# If you're building an application, you may want to check-in your pubspec.lock
|
# If you're building an application, you may want to check-in your pubspec.lock
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
import 'package:build/build.dart';
|
import 'package:build/build.dart';
|
||||||
import 'package:moor_generator/src/dao_generator.dart';
|
import 'package:moor_generator/src/backends/build/moor_builder.dart';
|
||||||
import 'package:moor_generator/src/state/options.dart';
|
|
||||||
import 'package:source_gen/source_gen.dart';
|
|
||||||
import 'package:moor_generator/src/moor_generator.dart';
|
|
||||||
|
|
||||||
Builder moorBuilder(BuilderOptions options) {
|
Builder moorBuilder(BuilderOptions options) => MoorBuilder(options);
|
||||||
final parsedOptions = MoorOptions.fromBuilder(options.config);
|
|
||||||
|
|
||||||
return SharedPartBuilder(
|
|
||||||
[
|
|
||||||
MoorGenerator(parsedOptions),
|
|
||||||
DaoGenerator(parsedOptions),
|
|
||||||
],
|
|
||||||
'moor',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
part of 'parser.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';
|
||||||
|
|
||||||
|
const 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';
|
||||||
|
|
||||||
|
/// Parses a single column defined in a Dart table. These columns are a chain
|
||||||
|
/// or [MethodInvocation]s. An example getter might look like this:
|
||||||
|
/// ```dart
|
||||||
|
/// IntColumn get id => integer().autoIncrement()();
|
||||||
|
/// ```
|
||||||
|
/// The last call `()` is a [FunctionExpressionInvocation], the entries for
|
||||||
|
/// before that (in this case `autoIncrement()` and `integer()` are a)
|
||||||
|
/// [MethodInvocation]. We work our way through that syntax until we hit a
|
||||||
|
/// method that starts the chain (contained in [starters]). By visiting all
|
||||||
|
/// the invocations on our way, we can extract the constraint for the column
|
||||||
|
/// (e.g. its name, whether it has auto increment, is a primary key and so on).
|
||||||
|
class ColumnParser {
|
||||||
|
final MoorDartParser base;
|
||||||
|
|
||||||
|
ColumnParser(this.base);
|
||||||
|
|
||||||
|
SpecifiedColumn parse(MethodDeclaration getter, MethodElement element) {}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:analyzer/dart/analysis/results.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:analyzer/dart/element/element.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';
|
||||||
|
|
||||||
|
part 'column_parser.dart';
|
||||||
|
|
||||||
|
class MoorDartParser {
|
||||||
|
final DartTask task;
|
||||||
|
|
||||||
|
MoorDartParser(this.task);
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Expression returnExpressionOfMethod(MethodDeclaration method) {
|
||||||
|
final body = method.body;
|
||||||
|
|
||||||
|
if (!(body is ExpressionFunctionBody)) {
|
||||||
|
task.reportError(ErrorInDartCode(
|
||||||
|
affectedElement: method.declaredElement,
|
||||||
|
severity: Severity.criticalError,
|
||||||
|
message:
|
||||||
|
'This method must have an expression body (user => instead of {return ...})',
|
||||||
|
));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (method.body as ExpressionFunctionBody).expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ElementDeclarationResult> loadElementDeclaration(
|
||||||
|
Element element) async {
|
||||||
|
final resolvedLibrary = await element.library.session
|
||||||
|
.getResolvedLibraryByElement(element.library);
|
||||||
|
|
||||||
|
return resolvedLibrary.getElementDeclaration(element);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
/// Base class for errors that can be presented to an user.
|
||||||
|
class MoorError {
|
||||||
|
final Severity severity;
|
||||||
|
|
||||||
|
MoorError(this.severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorInDartCode extends MoorError {
|
||||||
|
final String message;
|
||||||
|
final Element affectedElement;
|
||||||
|
|
||||||
|
ErrorInDartCode(
|
||||||
|
{this.message,
|
||||||
|
this.affectedElement,
|
||||||
|
Severity severity = Severity.warning})
|
||||||
|
: super(severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorSink {
|
||||||
|
final List<MoorError> _errors = [];
|
||||||
|
UnmodifiableListView<MoorError> get errors => UnmodifiableListView(_errors);
|
||||||
|
|
||||||
|
void report(MoorError error) {
|
||||||
|
_errors.add(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Severity {
|
||||||
|
/// A severe error. We might not be able to generate correct or consistent
|
||||||
|
/// code when errors with these severity are present.
|
||||||
|
criticalError,
|
||||||
|
|
||||||
|
/// An error. The generated code won't have major problems, but might cause
|
||||||
|
/// runtime errors. For instance, this is used when we get sql that has
|
||||||
|
/// semantic errors.
|
||||||
|
error,
|
||||||
|
|
||||||
|
warning,
|
||||||
|
info,
|
||||||
|
hint
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
|
||||||
|
/// Inputs coming from an external system (such as the analyzer, the build
|
||||||
|
/// package, etc.) that will be further analyzed by moor.
|
||||||
|
abstract class Input {
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
Input(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input for Dart files that have already been analyzed.
|
||||||
|
class DartInput extends Input {
|
||||||
|
final LibraryElement library;
|
||||||
|
|
||||||
|
DartInput(String path, this.library) : super(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input for a `.moor` file
|
||||||
|
class MoorInput extends Input {
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
MoorInput(String path, this.content) : super(path);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:analyzer/dart/element/element.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';
|
||||||
|
|
||||||
|
abstract class ParsedFile {}
|
||||||
|
|
||||||
|
class ParsedDartFile extends ParsedFile {
|
||||||
|
final LibraryElement library;
|
||||||
|
|
||||||
|
final List<SpecifiedTable> declaredTables;
|
||||||
|
final List<SpecifiedDao> declaredDaos;
|
||||||
|
final List<SpecifiedDatabase> declaredDatabases;
|
||||||
|
|
||||||
|
ParsedDartFile(
|
||||||
|
{@required this.library,
|
||||||
|
this.declaredTables = const [],
|
||||||
|
this.declaredDaos = const [],
|
||||||
|
this.declaredDatabases = const []});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParsedMoorFile extends ParsedFile {
|
||||||
|
final List<SpecifiedTable> declaredTables;
|
||||||
|
|
||||||
|
ParsedMoorFile(this.declaredTables);
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/errors.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/results.dart';
|
||||||
|
import 'package:moor_generator/src/backends/backend.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);
|
||||||
|
return DartTask(this, backendTask, library);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to parse and analyze a single file.
|
||||||
|
abstract class FileTask<R extends ParsedFile> {
|
||||||
|
final BackendTask backendTask;
|
||||||
|
final MoorSession session;
|
||||||
|
|
||||||
|
final ErrorSink errors = ErrorSink();
|
||||||
|
|
||||||
|
FileTask(this.backendTask, this.session);
|
||||||
|
|
||||||
|
void reportError(MoorError error) => errors.report(error);
|
||||||
|
|
||||||
|
FutureOr<R> compute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Session used to parse a Dart file and extract table information.
|
||||||
|
class DartTask extends FileTask<ParsedDartFile> {
|
||||||
|
final LibraryElement library;
|
||||||
|
|
||||||
|
DartTask(MoorSession session, BackendTask task, this.library)
|
||||||
|
: super(task, session);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<ParsedDartFile> compute() {
|
||||||
|
// TODO: implement compute
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoorTask extends FileTask<ParsedMoorFile> {
|
||||||
|
final List<String> content;
|
||||||
|
|
||||||
|
MoorTask(BackendTask task, MoorSession session, this.content)
|
||||||
|
: super(task, session);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<ParsedMoorFile> compute() {
|
||||||
|
// TODO: implement compute
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/session.dart';
|
||||||
|
|
||||||
|
/// A backend for the moor generator.
|
||||||
|
///
|
||||||
|
/// Currently, we only have a backend based on the build package, but we can
|
||||||
|
/// extend this to a backend for an analyzer plugin or a standalone tool.
|
||||||
|
abstract class Backend {
|
||||||
|
final MoorSession session = MoorSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to analyze a single file via ([entrypoint]). The other methods can be
|
||||||
|
/// used to read imports used by the other files.
|
||||||
|
abstract class BackendTask {
|
||||||
|
String get entrypoint;
|
||||||
|
|
||||||
|
Future<LibraryElement> resolveDart(String path);
|
||||||
|
Future<String> readMoor(String path);
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:build/build.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';
|
||||||
|
|
||||||
|
class MoorBuilder extends SharedPartBuilder {
|
||||||
|
factory MoorBuilder(BuilderOptions options) {
|
||||||
|
final parsedOptions = MoorOptions.fromBuilder(options.config);
|
||||||
|
|
||||||
|
final generators = [
|
||||||
|
MoorGenerator(parsedOptions),
|
||||||
|
DaoGenerator(parsedOptions),
|
||||||
|
];
|
||||||
|
|
||||||
|
return MoorBuilder._(generators, 'moor');
|
||||||
|
}
|
||||||
|
|
||||||
|
MoorBuilder._(List<Generator> generators, String name)
|
||||||
|
: super(generators, name);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:moor_generator/src/analyzer/dart/parser.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../../utils/test_backend.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('return expression of methods', () async {
|
||||||
|
final backend = TestBackend({
|
||||||
|
'test_lib|main.dart': r'''
|
||||||
|
class Test {
|
||||||
|
String get getter => 'foo';
|
||||||
|
String function() => 'bar';
|
||||||
|
String invalid() {
|
||||||
|
return 'baz';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
});
|
||||||
|
|
||||||
|
final backendTask = backend.startTask('test_lib|main.dart');
|
||||||
|
final dartTask = await backend.session.startDartTask(backendTask);
|
||||||
|
final parser = MoorDartParser(dartTask);
|
||||||
|
|
||||||
|
Future<MethodDeclaration> _loadDeclaration(Element element) async {
|
||||||
|
final declaration = await parser.loadElementDeclaration(element);
|
||||||
|
return declaration.node as MethodDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _verifyReturnExpressionMatches(Element element, String source) async {
|
||||||
|
final node = await _loadDeclaration(element);
|
||||||
|
expect(parser.returnExpressionOfMethod(node).toSource(), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
final testClass = dartTask.library.getType('Test');
|
||||||
|
|
||||||
|
_verifyReturnExpressionMatches(testClass.getGetter('getter'), "'foo'");
|
||||||
|
_verifyReturnExpressionMatches(testClass.getMethod('function'), "'bar'");
|
||||||
|
|
||||||
|
final invalidDecl = await _loadDeclaration(testClass.getMethod('invalid'));
|
||||||
|
expect(parser.returnExpressionOfMethod(invalidDecl), isNull);
|
||||||
|
expect(dartTask.errors.errors, isNotEmpty);
|
||||||
|
|
||||||
|
backend.finish();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
import 'package:build_test/build_test.dart';
|
||||||
|
import 'package:moor_generator/src/backends/backend.dart';
|
||||||
|
|
||||||
|
class TestBackend extends Backend {
|
||||||
|
final Map<String, String> fakeContent;
|
||||||
|
Resolver _resolver;
|
||||||
|
|
||||||
|
final Completer _initCompleter = Completer();
|
||||||
|
final Completer _finish = Completer();
|
||||||
|
|
||||||
|
/// Future that completes when this backend is ready, which happens when all
|
||||||
|
/// input files have been parsed and analyzed by the Dart analyzer.
|
||||||
|
Future get _ready => _initCompleter.future;
|
||||||
|
|
||||||
|
TestBackend(this.fakeContent) {
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _init() {
|
||||||
|
resolveSources(fakeContent, (r) {
|
||||||
|
_resolver = r;
|
||||||
|
_initCompleter.complete();
|
||||||
|
return _finish.future;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BackendTask startTask(String path) {
|
||||||
|
return _TestBackendTask(this, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish() {
|
||||||
|
_finish.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestBackendTask extends BackendTask {
|
||||||
|
final TestBackend backend;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String entrypoint;
|
||||||
|
|
||||||
|
_TestBackendTask(this.backend, this.entrypoint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> readMoor(String path) async {
|
||||||
|
await backend._ready;
|
||||||
|
return backend.fakeContent[path];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LibraryElement> resolveDart(String path) async {
|
||||||
|
await backend._ready;
|
||||||
|
return await backend._resolver.libraryFor(AssetId.parse(path));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue