From 9f922c928553c6dd377c5db4128e801db0103ca5 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 16 Oct 2017 10:50:09 -0700 Subject: [PATCH] Next update: closer to a final release. (#141) * Fix a number of issues. * OK, add some tests. * Add closures, some getters. * Another refactor. * Added FunctionType. * Updated README. * Dartfmt. * Fix CHANGELOG. --- CHANGELOG.md | 13 +- README.md | 8 +- lib/code_builder.dart | 3 +- lib/src/emitter.dart | 94 ++++++++++--- lib/src/specs/annotation.g.dart | 4 + lib/src/specs/class.g.dart | 4 + lib/src/specs/code.dart | 2 +- lib/src/specs/code.g.dart | 4 + lib/src/specs/constructor.g.dart | 4 + lib/src/specs/directive.g.dart | 4 + lib/src/specs/expression.dart | 130 ++++++++---------- lib/src/specs/expression/closure.dart | 25 ++++ lib/src/specs/expression/code.dart | 1 + lib/src/specs/expression/invoke.dart | 10 +- lib/src/specs/field.g.dart | 4 + lib/src/specs/file.g.dart | 4 + lib/src/specs/method.dart | 7 + lib/src/specs/method.g.dart | 5 +- lib/src/specs/reference.dart | 66 ++++++++- lib/src/specs/type_function.dart | 119 ++++++++++++++++ lib/src/specs/type_function.g.dart | 189 ++++++++++++++++++++++++++ lib/src/specs/type_reference.dart | 66 ++++++++- lib/src/specs/type_reference.g.dart | 4 + lib/src/visitors.dart | 3 + test/e2e/injection_test.dart | 4 +- test/specs/class_test.dart | 14 +- test/specs/code/expression_test.dart | 71 +++++++++- test/specs/code/statement_test.dart | 2 +- test/specs/file_test.dart | 2 +- test/specs/method_test.dart | 10 +- 30 files changed, 753 insertions(+), 123 deletions(-) create mode 100644 lib/src/specs/expression/closure.dart create mode 100644 lib/src/specs/type_function.dart create mode 100644 lib/src/specs/type_function.g.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7839e..74d96c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,23 @@ ## 2.0.0-alpha+3 +* Added `Expression.annotation` and `Expression.annotationNamed`. +* Added `Method.closure` to create an `Expression`. +* Added `FunctionType`. * Added `{new|const}InstanceNamed` to `Expression` [#135](https://github.com/dart-lang/code_builder/issues/135). * Also added a `typeArguments` option to all invocations. -* **BUG FIX**: `Block` now implements `Code` [#136](https://github.com/dart-lang/code_builder/issues/136). * Added `assign{...}` variants to `Expression` [#137](https://github.com/dart-lang/code_builder/issues/137). * Added `.awaited` and `.returned` to `Expression` [#138](https://github.com/dart-lang/code_builder/issues/138). + +* **BUG FIX**: `Block` now implements `Code` [#136](https://github.com/dart-lang/code_builder/issues/136). * **BUG FIX**: `new DartEmitter.scoped()` applies prefixing [#139](https://github.com/dart-lang/code_builder/issues/139). +* Renamed many of the `.asFoo(...)` and `.toFoo(...)` methods to single getter: + * `asCode()` to `code` + * `asStatement()` to `statement` + * `toExpression()` to `expression` + +* Moved `{new|const}Instance{[Named]}` from `Expression` to `Reference`. + ## 2.0.0-alpha+2 * Upgraded `build_runner` from `^0.3.0` to `>=0.4.0 <0.6.0`. diff --git a/README.md b/README.md index b06de52..7f959be 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ import 'package:dart_style/dart_style.dart'; void main() { final animal = new Class((b) => b ..name = 'Animal' - ..extend = refer('Organism').toType() + ..extend = refer('Organism') ..methods.add(new Method.returnsVoid((b) => b ..name = 'eat' ..lambda = true ..body = const Code('print(\'Yum\')')))); - final emitter = const DartEmitter(); + final emitter = new DartEmitter(); print(new DartFormatter().format('${animal.accept(emitter)}')); } ``` @@ -68,11 +68,11 @@ import 'package:dart_style/dart_style.dart'; void main() { final library = new File((b) => b.body.addAll([ new Method((b) => b - ..body = new Code((b) => b.code = '') + ..body = const Code('') ..name = 'doThing' ..returns = refer('Thing', 'package:a/a.dart')), new Method((b) => b - ..body = new Code((b) => b..code = '') + ..body = const Code('') ..name = 'doOther' ..returns = refer('Other', 'package:b/b.dart')), ])); diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 077d5c2..e8acbbe 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -15,7 +15,7 @@ export 'src/specs/directive.dart' show Directive, DirectiveType, DirectiveBuilder; export 'src/specs/expression.dart' show - AsCodeExpression, + ToCodeExpression, BinaryExpression, CodeExpression, Expression, @@ -46,4 +46,5 @@ export 'src/specs/method.dart' Parameter, ParameterBuilder; export 'src/specs/reference.dart' show refer, Reference; +export 'src/specs/type_function.dart' show FunctionType, FunctionTypeBuilder; export 'src/specs/type_reference.dart' show TypeReference, TypeReferenceBuilder; diff --git a/lib/src/emitter.dart b/lib/src/emitter.dart index 611798b..79650b8 100644 --- a/lib/src/emitter.dart +++ b/lib/src/emitter.dart @@ -16,6 +16,7 @@ import 'specs/field.dart'; import 'specs/file.dart'; import 'specs/method.dart'; import 'specs/reference.dart'; +import 'specs/type_function.dart'; import 'specs/type_reference.dart'; import 'visitors.dart'; @@ -80,27 +81,38 @@ class DartEmitter extends Object output.write('abstract '); } output.write('class ${spec.name}'); - visitTypeParameters(spec.types.map((r) => r.toType()), output); + visitTypeParameters(spec.types.map((r) => r.type), output); if (spec.extend != null) { output.write(' extends '); - visitType(spec.extend.toType(), output); + visitType(spec.extend.type, output); } if (spec.mixins.isNotEmpty) { output ..write(' with ') - ..writeAll( - spec.mixins.map((m) => visitType(m.toType())), ','); + ..writeAll(spec.mixins.map((m) => visitType(m.type)), ','); } if (spec.implements.isNotEmpty) { output ..write(' implements ') ..writeAll( - spec.implements.map((m) => visitType(m.toType())), ','); + spec.implements.map((m) => visitType(m.type)), ','); } output.write(' {'); - spec.constructors.forEach((c) => visitConstructor(c, spec.name, output)); - spec.fields.forEach((f) => visitField(f, output)); - spec.methods.forEach((m) => visitMethod(m, output)); + spec.constructors.forEach((c) { + visitConstructor(c, spec.name, output); + output.writeln(); + }); + spec.fields.forEach((f) { + visitField(f, output); + output.writeln(); + }); + spec.methods.forEach((m) { + visitMethod(m, output); + if (m.lambda) { + output.write(';'); + } + output.writeln(); + }); output.writeln(' }'); return output; } @@ -170,7 +182,7 @@ class DartEmitter extends Object } if (spec.redirect != null) { output.write(' = '); - visitType(spec.redirect.toType(), output); + visitType(spec.redirect.type, output); output.write(';'); } else if (spec.body != null) { if (spec.lambda) { @@ -239,7 +251,7 @@ class DartEmitter extends Object break; } if (spec.type != null) { - visitType(spec.type.toType(), output); + visitType(spec.type.type, output); output.write(' '); } output.write(spec.name); @@ -258,6 +270,9 @@ class DartEmitter extends Object final body = new StringBuffer(); for (final spec in spec.body) { body.write(visitSpec(spec)); + if (spec is Method && spec.lambda) { + output.write(';'); + } } // TODO: Allow some sort of logical ordering. for (final directive in spec.directives) { @@ -270,6 +285,46 @@ class DartEmitter extends Object return output; } + @override + visitFunctionType(FunctionType spec, [StringSink output]) { + output ??= new StringBuffer(); + if (spec.returnType != null) { + spec.returnType.accept(this, output); + output.write(' '); + } + output.write('Function'); + if (spec.types.isNotEmpty) { + output.write('<'); + visitAll(spec.types, output, (spec) { + spec.accept(this, output); + }); + output.write('>'); + } + output.write('('); + visitAll(spec.requiredParameters, output, (spec) { + spec.accept(this, output); + }); + if (spec.optionalParameters.isNotEmpty || + spec.namedParameters.isNotEmpty && spec.requiredParameters.isNotEmpty) { + output.write(', '); + } + if (spec.optionalParameters.isNotEmpty) { + output.write('['); + visitAll(spec.optionalParameters, output, (spec) { + spec.accept(this, output); + }); + output.write(']'); + } else if (spec.namedParameters.isNotEmpty) { + output.write('{'); + visitAll(spec.namedParameters.keys, output, (name) { + spec.namedParameters[name].accept(this, output); + output..write(' ')..write(name); + }); + output.write('}'); + } + return output..write(')'); + } + @override visitMethod(Method spec, [StringSink output]) { output ??= new StringBuffer(); @@ -282,7 +337,7 @@ class DartEmitter extends Object output.write('static '); } if (spec.returns != null) { - visitType(spec.returns.toType(), output); + visitType(spec.returns.type, output); output.write(' '); } if (spec.type == MethodType.getter) { @@ -291,8 +346,10 @@ class DartEmitter extends Object if (spec.type == MethodType.setter) { output.write('set '); } - output.write(spec.name); - visitTypeParameters(spec.types.map((r) => r.toType()), output); + if (spec.name != null) { + output.write(spec.name); + } + visitTypeParameters(spec.types.map((r) => r.type), output); output.write('('); if (spec.requiredParameters.isNotEmpty) { var count = 0; @@ -348,15 +405,12 @@ class DartEmitter extends Object output.write(' { '); } spec.body.accept(this, output); - if (spec.lambda) { - output.write(';'); - } else { + if (!spec.lambda) { output.write(' } '); } } else { output.write(';'); } - output.writeln(); return output; } @@ -370,7 +424,7 @@ class DartEmitter extends Object spec.docs.forEach(output.writeln); spec.annotations.forEach((a) => visitAnnotation(a, output)); if (spec.type != null) { - visitType(spec.type.toType(), output); + visitType(spec.type.type, output); output.write(' '); } if (spec.toThis) { @@ -401,9 +455,9 @@ class DartEmitter extends Object visitReference(spec, output); if (spec.bound != null) { output.write(' extends '); - visitType(spec.bound.toType(), output); + visitType(spec.bound.type, output); } - visitTypeParameters(spec.types.map((r) => r.toType()), output); + visitTypeParameters(spec.types.map((r) => r.type), output); return output; } diff --git a/lib/src/specs/annotation.g.dart b/lib/src/specs/annotation.g.dart index f5ff5c7..4a572ec 100644 --- a/lib/src/specs/annotation.g.dart +++ b/lib/src/specs/annotation.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.annotation; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/class.g.dart b/lib/src/specs/class.g.dart index f0ea47c..25d4ba8 100644 --- a/lib/src/specs/class.g.dart +++ b/lib/src/specs/class.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.class_; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/code.dart b/lib/src/specs/code.dart index 1cbafca..9160b2d 100644 --- a/lib/src/specs/code.dart +++ b/lib/src/specs/code.dart @@ -70,7 +70,7 @@ abstract class BlockBuilder implements Builder { /// /// **NOTE**: Not all expressions are _useful_ statements. void addExpression(Expression expression) { - statements.add(expression.asStatement()); + statements.add(expression.statement); } ListBuilder statements = new ListBuilder(); diff --git a/lib/src/specs/code.g.dart b/lib/src/specs/code.g.dart index c8d74bc..b012536 100644 --- a/lib/src/specs/code.g.dart +++ b/lib/src/specs/code.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.code; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/constructor.g.dart b/lib/src/specs/constructor.g.dart index b33795d..8b2cd4b 100644 --- a/lib/src/specs/constructor.g.dart +++ b/lib/src/specs/constructor.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.constructor; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/directive.g.dart b/lib/src/specs/directive.g.dart index 035c7ac..2a59c66 100644 --- a/lib/src/specs/directive.g.dart +++ b/lib/src/specs/directive.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.directive; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/expression.dart b/lib/src/specs/expression.dart index 189a438..6203ed3 100644 --- a/lib/src/specs/expression.dart +++ b/lib/src/specs/expression.dart @@ -9,14 +9,20 @@ import 'package:meta/meta.dart'; import '../base.dart'; import '../emitter.dart'; import '../visitors.dart'; +import 'annotation.dart'; import 'code.dart'; +import 'method.dart'; import 'reference.dart'; +import 'type_function.dart'; part 'expression/binary.dart'; +part 'expression/closure.dart'; part 'expression/code.dart'; part 'expression/invoke.dart'; part 'expression/literal.dart'; +/// Represents a [code] block that wraps an [Expression]. + /// Represents a Dart expression. /// /// See various concrete implementations for details. @@ -26,20 +32,20 @@ abstract class Expression implements Spec { @override R accept(covariant ExpressionVisitor visitor, [R context]); - /// Returns the expression as a valid [Code] block. + /// The expression as a valid [Code] block. /// - /// Also see [asStatement]. - Code asCode() => new AsCodeExpression(this, false); + /// Also see [statement]. + Code get code => new ToCodeExpression(this, false); - /// Returns the expression asa valid [Code] block with a trailing `;`. - Code asStatement() => new AsCodeExpression(this, true); + /// The expression as a valid [Code] block with a trailing `;`. + Code get statement => new ToCodeExpression(this, true); /// Returns the result of [this] `&&` [other]. Expression and(Expression other) { - return new BinaryExpression._(toExpression(), other, '&&'); + return new BinaryExpression._(expression, other, '&&'); } - /// This expression preceding by `await`. + /// This expression preceded by `await`. Expression get awaited { return new BinaryExpression._( const LiteralExpression._('await'), @@ -51,8 +57,8 @@ abstract class Expression implements Spec { /// Return `{other} = {this}`. Expression assign(Expression other) { return new BinaryExpression._( - other, this, + other, '=', ); } @@ -60,8 +66,8 @@ abstract class Expression implements Spec { /// Return `{other} ??= {this}`. Expression assignNullAware(Expression other) { return new BinaryExpression._( - other, this, + other, '??=', ); } @@ -72,7 +78,7 @@ abstract class Expression implements Spec { type == null ? new LiteralExpression._('var $name') : new BinaryExpression._( - type.toExpression(), + type.expression, new LiteralExpression._(name), '', ), @@ -88,7 +94,7 @@ abstract class Expression implements Spec { ? const LiteralExpression._('final') : new BinaryExpression._( const LiteralExpression._('final'), - type.toExpression(), + type.expression, '', ), this, @@ -103,7 +109,7 @@ abstract class Expression implements Spec { ? const LiteralExpression._('const') : new BinaryExpression._( const LiteralExpression._('const'), - type.toExpression(), + type.expression, '', ), this, @@ -135,69 +141,42 @@ abstract class Expression implements Spec { ); } - /// Returns a new instance of this expression. - Expression newInstance( - List positionalArguments, [ + /// Returns an annotation as a result of calling this constructor. + Annotation annotation([ + List positionalArguments, Map namedArguments = const {}, List typeArguments = const [], ]) { - return new InvokeExpression._new( - this, - positionalArguments, - namedArguments, - typeArguments, - null, - ); - } - - /// Returns a new instance of this expression with a named constructor. - Expression newInstanceNamed( - String name, - List positionalArguments, [ - Map namedArguments = const {}, - List typeArguments = const [], - ]) { - return new InvokeExpression._new( - this, - positionalArguments, - namedArguments, - typeArguments, - name, - ); - } - - /// Returns a const instance of this expression. - Expression constInstance( - List positionalArguments, [ - Map namedArguments = const {}, - List typeArguments = const [], - ]) { - return new InvokeExpression._const( - this, - positionalArguments, - namedArguments, - typeArguments, - null, - ); + if (positionalArguments == null) { + return new Annotation((b) { + b.code = code; + }); + } + return new Annotation((b) { + b.code = new InvokeExpression._( + this, + positionalArguments, + namedArguments, + typeArguments, + ) + .code; + }); } - /// Returns a const instance of this expression with a named constructor. - Expression constInstanceNamed( + /// Returns an annotation as a result of calling a named constructor. + Annotation annotationNamed( String name, List positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], ]) { - return new InvokeExpression._const( - this, - positionalArguments, - namedArguments, - typeArguments, - name, - ); + return new Annotation((b) => b + ..code = new InvokeExpression._( + this, positionalArguments, namedArguments, typeArguments, name) + .code); } - /// This expression preceding by `return`. + /// This expression preceded by `return`. Expression get returned { return new BinaryExpression._( const LiteralExpression._('return'), @@ -208,23 +187,27 @@ abstract class Expression implements Spec { /// May be overridden to support other types implementing [Expression]. @visibleForOverriding - Expression toExpression() => this; + Expression get expression => this; } -/// Represents a [code] block that wraps an [Expression]. -class AsCodeExpression implements Code { +/// Creates `typedef {name} =`. +Code createTypeDef(String name, FunctionType type) => new BinaryExpression._( + new LiteralExpression._('typedef $name'), type.expression, '=') + .statement; + +class ToCodeExpression implements Code { final Expression code; /// Whether this code should be considered a _statement_. final bool isStatement; @visibleForTesting - const AsCodeExpression(this.code, [this.isStatement = false]); + const ToCodeExpression(this.code, [this.isStatement = false]); @override R accept(CodeVisitor visitor, [R context]) { return (visitor as ExpressionVisitor) - .visitAsCodeExpression(this, context); + .visitToCodeExpression(this, context); } @override @@ -235,8 +218,9 @@ class AsCodeExpression implements Code { /// /// **INTERNAL ONLY**. abstract class ExpressionVisitor implements SpecVisitor { - T visitAsCodeExpression(AsCodeExpression code, [T context]); + T visitToCodeExpression(ToCodeExpression code, [T context]); T visitBinaryExpression(BinaryExpression expression, [T context]); + T visitClosureExpression(ClosureExpression expression, [T context]); T visitCodeExpression(CodeExpression expression, [T context]); T visitInvokeExpression(InvokeExpression expression, [T context]); T visitLiteralExpression(LiteralExpression expression, [T context]); @@ -249,7 +233,7 @@ abstract class ExpressionVisitor implements SpecVisitor { /// **INTERNAL ONLY**. abstract class ExpressionEmitter implements ExpressionVisitor { @override - visitAsCodeExpression(AsCodeExpression expression, [StringSink output]) { + visitToCodeExpression(ToCodeExpression expression, [StringSink output]) { output ??= new StringBuffer(); expression.code.accept(this, output); if (expression.isStatement) { @@ -273,6 +257,12 @@ abstract class ExpressionEmitter implements ExpressionVisitor { return output; } + @override + visitClosureExpression(ClosureExpression expression, [StringSink output]) { + output ??= new StringBuffer(); + return expression.method.accept(this, output); + } + @override visitCodeExpression(CodeExpression expression, [StringSink output]) { output ??= new StringBuffer(); diff --git a/lib/src/specs/expression/closure.dart b/lib/src/specs/expression/closure.dart new file mode 100644 index 0000000..e8600dc --- /dev/null +++ b/lib/src/specs/expression/closure.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of code_builder.src.specs.expression; + +@visibleForTesting +Expression toClosure(Method method) { + final withoutTypes = method.rebuild((b) { + b.returns = null; + b.types.clear(); + }); + return new ClosureExpression._(withoutTypes); +} + +class ClosureExpression extends Expression { + final Method method; + + const ClosureExpression._(this.method); + + @override + R accept(ExpressionVisitor visitor, [R context]) { + return visitor.visitClosureExpression(this, context); + } +} diff --git a/lib/src/specs/expression/code.dart b/lib/src/specs/expression/code.dart index aa79b83..026b859 100644 --- a/lib/src/specs/expression/code.dart +++ b/lib/src/specs/expression/code.dart @@ -6,6 +6,7 @@ part of code_builder.src.specs.expression; /// Represents a [Code] block as an [Expression]. class CodeExpression extends Expression { + @override final Code code; /// **INTERNAL ONLY**: Used to wrap [Code] as an [Expression]. diff --git a/lib/src/specs/expression/invoke.dart b/lib/src/specs/expression/invoke.dart index a81f5cc..84148be 100644 --- a/lib/src/specs/expression/invoke.dart +++ b/lib/src/specs/expression/invoke.dart @@ -22,11 +22,12 @@ class InvokeExpression extends Expression { this.positionalArguments, [ this.namedArguments = const {}, this.typeArguments, + this.name, ]) - : type = null, - name = null; + : type = null; - const InvokeExpression._new( + @visibleForTesting + const InvokeExpression.newOf( this.target, this.positionalArguments, [ this.namedArguments = const {}, @@ -35,7 +36,8 @@ class InvokeExpression extends Expression { ]) : type = InvokeExpressionType.newInstance; - const InvokeExpression._const( + @visibleForTesting + const InvokeExpression.constOf( this.target, this.positionalArguments, [ this.namedArguments = const {}, diff --git a/lib/src/specs/field.g.dart b/lib/src/specs/field.g.dart index 1d662b2..407ac89 100644 --- a/lib/src/specs/field.g.dart +++ b/lib/src/specs/field.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.field; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/file.g.dart b/lib/src/specs/file.g.dart index d2540ef..bbe5c77 100644 --- a/lib/src/specs/file.g.dart +++ b/lib/src/specs/file.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.library; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/specs/method.dart b/lib/src/specs/method.dart index 3734758..1de7dd6 100644 --- a/lib/src/specs/method.dart +++ b/lib/src/specs/method.dart @@ -15,6 +15,7 @@ import '../mixins/generics.dart'; import '../visitors.dart'; import 'annotation.dart'; import 'code.dart'; +import 'expression.dart'; import 'reference.dart'; part 'method.g.dart'; @@ -69,6 +70,9 @@ abstract class Method extends Object bool get static; /// Name of the method or function. + /// + /// May be `null` when being used as a [closure]. + @nullable String get name; /// Whether this is a getter or setter. @@ -88,6 +92,9 @@ abstract class Method extends Object R context, ]) => visitor.visitMethod(this, context); + + /// This method as a closure. + Expression get closure => toClosure(this); } abstract class MethodBuilder extends Object diff --git a/lib/src/specs/method.g.dart b/lib/src/specs/method.g.dart index 1417d42..1f1946e 100644 --- a/lib/src/specs/method.g.dart +++ b/lib/src/specs/method.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.method; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first @@ -66,7 +70,6 @@ class _$Method extends Method { if (external == null) throw new ArgumentError.notNull('external'); if (lambda == null) throw new ArgumentError.notNull('lambda'); if (static == null) throw new ArgumentError.notNull('static'); - if (name == null) throw new ArgumentError.notNull('name'); } @override diff --git a/lib/src/specs/reference.dart b/lib/src/specs/reference.dart index 78aa220..9bf296a 100644 --- a/lib/src/specs/reference.dart +++ b/lib/src/specs/reference.dart @@ -49,8 +49,70 @@ class Reference extends Expression implements Spec { bool operator ==(Object o) => o is Reference && o.url == url && o.symbol == symbol; + /// Returns a new instance of this expression. + Expression newInstance( + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.newOf( + this, + positionalArguments, + namedArguments, + typeArguments, + null, + ); + } + + /// Returns a new instance of this expression with a named constructor. + Expression newInstanceNamed( + String name, + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.newOf( + this, + positionalArguments, + namedArguments, + typeArguments, + name, + ); + } + + /// Returns a const instance of this expression. + Expression constInstance( + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.constOf( + this, + positionalArguments, + namedArguments, + typeArguments, + null, + ); + } + + /// Returns a const instance of this expression with a named constructor. + Expression constInstanceNamed( + String name, + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.constOf( + this, + positionalArguments, + namedArguments, + typeArguments, + name, + ); + } + @override - Expression toExpression() { + Expression get expression { return new CodeExpression(new Code.scope((a) => a(this))); } @@ -61,7 +123,7 @@ class Reference extends Expression implements Spec { .toString(); /// Returns as a [TypeReference], which allows adding generic type parameters. - TypeReference toType() => new TypeReference((b) => b + TypeReference get type => new TypeReference((b) => b ..url = url ..symbol = symbol); } diff --git a/lib/src/specs/type_function.dart b/lib/src/specs/type_function.dart new file mode 100644 index 0000000..3abc4db --- /dev/null +++ b/lib/src/specs/type_function.dart @@ -0,0 +1,119 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library code_builder.src.specs.type_function; + +import 'package:built_value/built_value.dart'; +import 'package:built_collection/built_collection.dart'; +import 'package:meta/meta.dart'; + +import '../base.dart'; +import '../mixins/generics.dart'; +import '../visitors.dart'; +import 'code.dart'; +import 'expression.dart'; +import 'reference.dart'; +import 'type_reference.dart'; + +part 'type_function.g.dart'; + +@immutable +abstract class FunctionType extends Expression + with HasGenerics + implements Built, Reference, Spec { + factory FunctionType([ + void updates(FunctionTypeBuilder b), + ]) = _$FunctionType; + + FunctionType._(); + + @override + R accept( + SpecVisitor visitor, [ + R context, + ]) => + visitor.visitFunctionType(this, context); + + /// Return type. + @nullable + Reference get returnType; + + @override + BuiltList get types; + + /// Required positional arguments to this function type. + BuiltList get requiredParameters; + + /// Optional positional arguments to this function type. + BuiltList get optionalParameters; + + /// Named optional arguments to this function type. + BuiltMap get namedParameters; + + @override + String get url => null; + + @override + String get symbol => null; + + @override + TypeReference get type => null; + + @override + Expression newInstance( + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) => + throw new UnsupportedError('Cannot "new" a function type.'); + + @override + Expression newInstanceNamed( + String name, + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) => + throw new UnsupportedError('Cannot "new" a function type.'); + + @override + Expression constInstance( + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) => + throw new UnsupportedError('Cannot "const" a function type.'); + + @override + Expression constInstanceNamed( + String name, + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) => + throw new UnsupportedError('Cannot "const" a function type.'); + + /// A typedef assignment to this type. + Code toTypeDef(String name) => createTypeDef(name, this); +} + +abstract class FunctionTypeBuilder extends Object + with HasGenericsBuilder + implements Builder { + factory FunctionTypeBuilder() = _$FunctionTypeBuilder; + + FunctionTypeBuilder._(); + + Reference returnType; + + @override + ListBuilder types = new ListBuilder(); + + ListBuilder requiredParameters = new ListBuilder(); + + ListBuilder optionalParameters = new ListBuilder(); + + MapBuilder namedParameters = + new MapBuilder(); +} diff --git a/lib/src/specs/type_function.g.dart b/lib/src/specs/type_function.g.dart new file mode 100644 index 0000000..2faa2d2 --- /dev/null +++ b/lib/src/specs/type_function.g.dart @@ -0,0 +1,189 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of code_builder.src.specs.type_function; + +// ************************************************************************** +// Generator: BuiltValueGenerator +// ************************************************************************** + +// ignore_for_file: always_put_control_body_on_new_line +// ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_expression_function_bodies +// ignore_for_file: sort_constructors_first + +class _$FunctionType extends FunctionType { + @override + final Reference returnType; + @override + final BuiltList types; + @override + final BuiltList requiredParameters; + @override + final BuiltList optionalParameters; + @override + final BuiltMap namedParameters; + + factory _$FunctionType([void updates(FunctionTypeBuilder b)]) => + (new FunctionTypeBuilder()..update(updates)).build() as _$FunctionType; + + _$FunctionType._( + {this.returnType, + this.types, + this.requiredParameters, + this.optionalParameters, + this.namedParameters}) + : super._() { + if (types == null) throw new ArgumentError.notNull('types'); + if (requiredParameters == null) + throw new ArgumentError.notNull('requiredParameters'); + if (optionalParameters == null) + throw new ArgumentError.notNull('optionalParameters'); + if (namedParameters == null) + throw new ArgumentError.notNull('namedParameters'); + } + + @override + FunctionType rebuild(void updates(FunctionTypeBuilder b)) => + (toBuilder()..update(updates)).build(); + + @override + _$FunctionTypeBuilder toBuilder() => + new _$FunctionTypeBuilder()..replace(this); + + @override + bool operator ==(dynamic other) { + if (identical(other, this)) return true; + if (other is! FunctionType) return false; + return returnType == other.returnType && + types == other.types && + requiredParameters == other.requiredParameters && + optionalParameters == other.optionalParameters && + namedParameters == other.namedParameters; + } + + @override + int get hashCode { + return $jf($jc( + $jc( + $jc($jc($jc(0, returnType.hashCode), types.hashCode), + requiredParameters.hashCode), + optionalParameters.hashCode), + namedParameters.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('FunctionType') + ..add('returnType', returnType) + ..add('types', types) + ..add('requiredParameters', requiredParameters) + ..add('optionalParameters', optionalParameters) + ..add('namedParameters', namedParameters)) + .toString(); + } +} + +class _$FunctionTypeBuilder extends FunctionTypeBuilder { + _$FunctionType _$v; + + @override + Reference get returnType { + _$this; + return super.returnType; + } + + @override + set returnType(Reference returnType) { + _$this; + super.returnType = returnType; + } + + @override + ListBuilder get types { + _$this; + return super.types ??= new ListBuilder(); + } + + @override + set types(ListBuilder types) { + _$this; + super.types = types; + } + + @override + ListBuilder get requiredParameters { + _$this; + return super.requiredParameters ??= new ListBuilder(); + } + + @override + set requiredParameters(ListBuilder requiredParameters) { + _$this; + super.requiredParameters = requiredParameters; + } + + @override + ListBuilder get optionalParameters { + _$this; + return super.optionalParameters ??= new ListBuilder(); + } + + @override + set optionalParameters(ListBuilder optionalParameters) { + _$this; + super.optionalParameters = optionalParameters; + } + + @override + MapBuilder get namedParameters { + _$this; + return super.namedParameters ??= new MapBuilder(); + } + + @override + set namedParameters(MapBuilder namedParameters) { + _$this; + super.namedParameters = namedParameters; + } + + _$FunctionTypeBuilder() : super._(); + + FunctionTypeBuilder get _$this { + if (_$v != null) { + super.returnType = _$v.returnType; + super.types = _$v.types?.toBuilder(); + super.requiredParameters = _$v.requiredParameters?.toBuilder(); + super.optionalParameters = _$v.optionalParameters?.toBuilder(); + super.namedParameters = _$v.namedParameters?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(FunctionType other) { + if (other == null) throw new ArgumentError.notNull('other'); + _$v = other as _$FunctionType; + } + + @override + void update(void updates(FunctionTypeBuilder b)) { + if (updates != null) updates(this); + } + + @override + _$FunctionType build() { + final _$result = _$v ?? + new _$FunctionType._( + returnType: returnType, + types: types?.build(), + requiredParameters: requiredParameters?.build(), + optionalParameters: optionalParameters?.build(), + namedParameters: namedParameters?.build()); + replace(_$result); + return _$result; + } +} diff --git a/lib/src/specs/type_reference.dart b/lib/src/specs/type_reference.dart index 68eedb1..d09d093 100644 --- a/lib/src/specs/type_reference.dart +++ b/lib/src/specs/type_reference.dart @@ -49,12 +49,74 @@ abstract class TypeReference extends Expression visitor.visitType(this, context); @override - Expression toExpression() { + Expression get expression { return new CodeExpression(new Code.scope((a) => a(this))); } @override - TypeReference toType() => this; + TypeReference get type => this; + + @override + Expression newInstance( + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.newOf( + this, + positionalArguments, + namedArguments, + typeArguments, + null, + ); + } + + @override + Expression newInstanceNamed( + String name, + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.newOf( + this, + positionalArguments, + namedArguments, + typeArguments, + name, + ); + } + + @override + Expression constInstance( + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.constOf( + this, + positionalArguments, + namedArguments, + typeArguments, + null, + ); + } + + @override + Expression constInstanceNamed( + String name, + List positionalArguments, [ + Map namedArguments = const {}, + List typeArguments = const [], + ]) { + return new InvokeExpression.constOf( + this, + positionalArguments, + namedArguments, + typeArguments, + name, + ); + } } abstract class TypeReferenceBuilder extends Object diff --git a/lib/src/specs/type_reference.g.dart b/lib/src/specs/type_reference.g.dart index 3b658ec..05d2719 100644 --- a/lib/src/specs/type_reference.g.dart +++ b/lib/src/specs/type_reference.g.dart @@ -6,7 +6,11 @@ part of code_builder.src.specs.type_reference; // Generator: BuiltValueGenerator // ************************************************************************** +// ignore_for_file: always_put_control_body_on_new_line // ignore_for_file: annotate_overrides +// ignore_for_file: avoid_annotating_with_dynamic +// ignore_for_file: avoid_returning_this +// ignore_for_file: omit_local_variable_types // ignore_for_file: prefer_expression_function_bodies // ignore_for_file: sort_constructors_first diff --git a/lib/src/visitors.dart b/lib/src/visitors.dart index 03aea23..111f497 100644 --- a/lib/src/visitors.dart +++ b/lib/src/visitors.dart @@ -13,6 +13,7 @@ import 'specs/field.dart'; import 'specs/file.dart'; import 'specs/method.dart'; import 'specs/reference.dart'; +import 'specs/type_function.dart'; import 'specs/type_reference.dart'; @optionalTypeArgs @@ -31,6 +32,8 @@ abstract class SpecVisitor { T visitFile(File spec, [T context]); + T visitFunctionType(FunctionType spec, [T context]); + T visitMethod(Method spec, [T context]); T visitReference(Reference spec, [T context]); diff --git a/test/e2e/injection_test.dart b/test/e2e/injection_test.dart index dc45ef4..a4a1ae6 100644 --- a/test/e2e/injection_test.dart +++ b/test/e2e/injection_test.dart @@ -18,7 +18,7 @@ void main() { ..fields.add(new Field((b) => b ..modifier = FieldModifier.final$ ..name = '_module' - ..type = $Module.toType())) + ..type = $Module.type)) ..constructors.add(new Constructor((b) => b ..requiredParameters.add(new Parameter((b) => b ..name = '_module' @@ -28,7 +28,7 @@ void main() { ..body = $Thing.newInstance([ refer('_module').property('get1').call([]), refer('_module').property('get2').call([]), - ]).asCode() + ]).code ..lambda = true ..returns = $Thing ..annotations diff --git a/test/specs/class_test.dart b/test/specs/class_test.dart index 54ba0a3..f05c41a 100644 --- a/test/specs/class_test.dart +++ b/test/specs/class_test.dart @@ -45,19 +45,19 @@ void main() { }); test('should create a class with annotations', () { - final $deprecated = refer('deprecated'); expect( new Class( (b) => b ..name = 'Foo' - ..annotations.add( - new Annotation( - (b) => b..code = new Code.scope((a) => a($deprecated)), - ), - ), + ..annotations.addAll([ + refer('deprecated').annotation(), + refer('Deprecated') + .annotation([literalString('This is an old class')]) + ]), ), equalsDart(r''' @deprecated + @Deprecated('This is an old class') class Foo {} '''), ); @@ -98,7 +98,7 @@ void main() { ..symbol = 'T' ..bound = new TypeReference((b) => b ..symbol = 'Comparable' - ..types.add(refer('T').toType()))))), + ..types.add(refer('T').type))))), equalsDart(r''' class Comparable> {} '''), diff --git a/test/specs/code/expression_test.dart b/test/specs/code/expression_test.dart index d5e5476..b93f6cf 100644 --- a/test/specs/code/expression_test.dart +++ b/test/specs/code/expression_test.dart @@ -174,13 +174,82 @@ void main() { ); }); + test('should emit a function type', () { + expect( + new FunctionType((b) => b.returnType = refer('void')), + equalsDart('void Function()'), + ); + }); + + test('should emit a typedef statement', () { + expect( + new FunctionType((b) => b.returnType = refer('void')).toTypeDef('Void0'), + equalsDart('typedef Void0 = void Function();'), + ); + }); + + test('should emit a function type with type parameters', () { + expect( + new FunctionType((b) => b + ..returnType = refer('T') + ..types.add(refer('T'))), + equalsDart('T Function()'), + ); + }); + + test('should emit a function type a single parameter', () { + expect( + new FunctionType((b) => b..requiredParameters.add(refer('String'))), + equalsDart('Function(String)'), + ); + }); + + test('should emit a function type with parameters', () { + expect( + new FunctionType((b) => b + ..requiredParameters.add(refer('String')) + ..optionalParameters.add(refer('int'))), + equalsDart('Function(String, [int])'), + ); + }); + + test('should emit a function type with named parameters', () { + expect( + new FunctionType((b) => b + ..namedParameters.addAll({ + 'x': refer('int'), + 'y': refer('int'), + })), + equalsDart('Function({int x, int y})'), + ); + }); + + test('should emit a closure', () { + expect( + refer('map').property('putIfAbsent').call([ + literalString('foo'), + new Method((b) => b + ..lambda = true + ..body = literalTrue.code).closure, + ]), + equalsDart("map.putIfAbsent('foo', () => true)"), + ); + }); + test('should emit an assignment', () { expect( - literalTrue.assign(refer('foo')), + refer('foo').assign(literalTrue), equalsDart('foo = true'), ); }); + test('should emit a null-aware assignment', () { + expect( + refer('foo').assignNullAware(literalTrue), + equalsDart('foo ??= true'), + ); + }); + test('should emit assigning to a var', () { expect( literalTrue.assignVar('foo'), diff --git a/test/specs/code/statement_test.dart b/test/specs/code/statement_test.dart index 834d2c7..d233bea 100644 --- a/test/specs/code/statement_test.dart +++ b/test/specs/code/statement_test.dart @@ -25,7 +25,7 @@ void main() { expect( new Block((b) => b.statements.addAll([ const Code('if (foo) {'), - refer('print')([literalTrue]).asStatement(), + refer('print')([literalTrue]).statement, const Code('}'), ])), equalsDart(r''' diff --git a/test/specs/file_test.dart b/test/specs/file_test.dart index 0818a0f..85b151f 100644 --- a/test/specs/file_test.dart +++ b/test/specs/file_test.dart @@ -16,7 +16,7 @@ void main() { ..body.add(new Field((b) => b ..name = 'test' ..modifier = FieldModifier.final$ - ..assignment = $LinkedHashMap.newInstance([]).asCode()))), + ..assignment = $LinkedHashMap.newInstance([]).code))), equalsDart(r''' import 'dart:collection'; diff --git a/test/specs/method_test.dart b/test/specs/method_test.dart index f73f2fd..cea286e 100644 --- a/test/specs/method_test.dart +++ b/test/specs/method_test.dart @@ -23,7 +23,7 @@ void main() { ..lambda = true ..body = const Code('null')), equalsDart(r''' - foo() async => null; + foo() async => null '''), ); }); @@ -36,7 +36,7 @@ void main() { ..lambda = true ..body = const Code('null')), equalsDart(r''' - foo() async* => null; + foo() async* => null '''), ); }); @@ -49,7 +49,7 @@ void main() { ..lambda = true ..body = const Code('null')), equalsDart(r''' - foo() sync* => null; + foo() sync* => null '''), ); }); @@ -141,7 +141,7 @@ void main() { ..lambda = true ..body = const Code('1 + 2')), equalsDart(r''' - foo() => 1 + 2; + foo() => 1 + 2 '''), ); }); @@ -204,7 +204,7 @@ void main() { new Parameter( (b) => b ..name = 'i' - ..type = refer('int').toType(), + ..type = refer('int').type, ), ), ),