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
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
# If you're building an application, you may want to check-in your pubspec.lock
|
||||
pubspec.lock
|
||||
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
import 'package:build/build.dart';
|
||||
import 'package:moor_generator/src/dao_generator.dart';
|
||||
import 'package:moor_generator/src/state/options.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
import 'package:moor_generator/src/moor_generator.dart';
|
||||
import 'package:moor_generator/src/backends/build/moor_builder.dart';
|
||||
|
||||
Builder moorBuilder(BuilderOptions options) {
|
||||
final parsedOptions = MoorOptions.fromBuilder(options.config);
|
||||
|
||||
return SharedPartBuilder(
|
||||
[
|
||||
MoorGenerator(parsedOptions),
|
||||
DaoGenerator(parsedOptions),
|
||||
],
|
||||
'moor',
|
||||
);
|
||||
}
|
||||
Builder moorBuilder(BuilderOptions options) => MoorBuilder(options);
|
||||
|
|
|
@ -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