diff --git a/pkgs/code_builder/CHANGELOG.md b/pkgs/code_builder/CHANGELOG.md index 2b429cac8..a45bafc33 100644 --- a/pkgs/code_builder/CHANGELOG.md +++ b/pkgs/code_builder/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.0-alpha+9 + +- Add support for `async`, `sync`, `sync*` functions +- Add support for expression `asAwait`, `asYield`, `asYieldStar` +- Add `toExportBuilder` and `toImportBuilder` to types and references + ## 1.0.0-alpha+8 - Fix an import scoping bug in `return` statements and named constructor invocations. diff --git a/pkgs/code_builder/lib/code_builder.dart b/pkgs/code_builder/lib/code_builder.dart index 283898c7f..ed6494ee9 100644 --- a/pkgs/code_builder/lib/code_builder.dart +++ b/pkgs/code_builder/lib/code_builder.dart @@ -23,6 +23,7 @@ export 'src/builders/method.dart' named, ConstructorBuilder, MethodBuilder, + MethodModifier, ValidMethodMember; export 'src/builders/parameter.dart' show parameter, ParameterBuilder; export 'src/pretty_printer.dart' show prettyToSource; diff --git a/pkgs/code_builder/lib/src/builders/class.dart b/pkgs/code_builder/lib/src/builders/class.dart index 88830e8e4..ea1794caa 100644 --- a/pkgs/code_builder/lib/src/builders/class.dart +++ b/pkgs/code_builder/lib/src/builders/class.dart @@ -7,6 +7,7 @@ import 'package:analyzer/dart/ast/standard_ast_factory.dart'; import 'package:code_builder/dart/core.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/field.dart'; +import 'package:code_builder/src/builders/file.dart'; import 'package:code_builder/src/builders/method.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/type.dart'; @@ -254,6 +255,16 @@ class _ClassBuilderImpl extends Object void setExtends(TypeBuilder extend) { _extends = extend; } + + @override + ExportBuilder toExportBuilder() { + throw new UnsupportedError('Not supported for ClassBuilder'); + } + + @override + ImportBuilder toImportBuilder({bool deferred: false, String prefix}) { + throw new UnsupportedError('Not supported for ClassBuilder'); + } } class _TypeNameWrapper implements ValidClassMember { diff --git a/pkgs/code_builder/lib/src/builders/expression.dart b/pkgs/code_builder/lib/src/builders/expression.dart index 56a04a304..b3f957d56 100644 --- a/pkgs/code_builder/lib/src/builders/expression.dart +++ b/pkgs/code_builder/lib/src/builders/expression.dart @@ -19,11 +19,13 @@ import 'package:code_builder/src/tokens.dart'; part 'expression/assert.dart'; part 'expression/assign.dart'; +part 'expression/await.dart'; part 'expression/cascade.dart'; part 'expression/invocation.dart'; part 'expression/negate.dart'; part 'expression/operators.dart'; part 'expression/return.dart'; +part 'expression/yield.dart'; final _false = astFactory.booleanLiteral(new KeywordToken(Keyword.FALSE, 0), true); @@ -141,6 +143,9 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { }) => new _AsAssign(this, variable, nullAware, target); + @override + ExpressionBuilder asAwait() => new _AsAwait(this); + @override StatementBuilder asConst(String variable, [TypeBuilder type]) { return new _AsAssignNew(this, variable, type, $const); @@ -165,6 +170,12 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { return new _AsAssignNew(this, variable, type, $var); } + @override + StatementBuilder asYield() => new _AsYield(this, false); + + @override + StatementBuilder asYieldStar() => new _AsYield(this, true); + @override Statement buildStatement([Scope scope]) { return asStatement().buildStatement(scope); @@ -273,8 +284,14 @@ abstract class ExpressionBuilder /// Returns as a [StatementBuilder] that assigns to an existing [variable]. /// /// If [target] is specified, determined to be `{target}.{variable}`. - StatementBuilder asAssign(String variable, - {ExpressionBuilder target, bool nullAware}); + StatementBuilder asAssign( + String variable, { + ExpressionBuilder target, + bool nullAware, + }); + + /// Returns as an expression `await`-ing this one. + ExpressionBuilder asAwait() => new _AsAwait(this); /// Returns as a [StatementBuilder] that assigns to a new `const` [variable]. StatementBuilder asConst(String variable, [TypeBuilder type]); @@ -300,6 +317,12 @@ abstract class ExpressionBuilder /// If [type] is supplied, the resulting statement is `{type} {variable} =`. StatementBuilder asVar(String variable, [TypeBuilder type]); + /// Returns as a [StatementBuilder] yielding this one. + StatementBuilder asYield(); + + /// Returns as a [StatementBuilder] yielding this one. + StatementBuilder asYieldStar(); + /// Returns an [Expression] AST representing the builder. Expression buildExpression([Scope scope]); diff --git a/pkgs/code_builder/lib/src/builders/expression/await.dart b/pkgs/code_builder/lib/src/builders/expression/await.dart new file mode 100644 index 000000000..5b15fabb0 --- /dev/null +++ b/pkgs/code_builder/lib/src/builders/expression/await.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2016, 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.builders.expression; + +class _AsAwait extends AbstractExpressionMixin with TopLevelMixin { + final ExpressionBuilder _expression; + + _AsAwait(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new AwaitExpression( + $await, + _expression.buildExpression(scope), + ); + } +} diff --git a/pkgs/code_builder/lib/src/builders/expression/yield.dart b/pkgs/code_builder/lib/src/builders/expression/yield.dart new file mode 100644 index 000000000..98742615d --- /dev/null +++ b/pkgs/code_builder/lib/src/builders/expression/yield.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2016, 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.builders.expression; + +class _AsYield extends TopLevelMixin implements StatementBuilder { + final ExpressionBuilder _expression; + final bool _isStar; + + _AsYield(this._expression, this._isStar); + + @override + Statement buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new YieldStatement( + $yield, + _isStar ? $star : null, + _expression.buildExpression(scope), + $semicolon, + ); + } +} diff --git a/pkgs/code_builder/lib/src/builders/method.dart b/pkgs/code_builder/lib/src/builders/method.dart index 5ba04f122..f2bbd3c11 100644 --- a/pkgs/code_builder/lib/src/builders/method.dart +++ b/pkgs/code_builder/lib/src/builders/method.dart @@ -18,20 +18,42 @@ import 'package:code_builder/src/builders/type.dart'; import 'package:code_builder/src/tokens.dart'; /// Short-hand for `new ConstructorBuilder(...)`. -ConstructorBuilder constructor( - [Iterable members = const []]) { +ConstructorBuilder constructor([ + Iterable members = const [], +]) { return _constructorImpl(members: members); } /// Short-hand for `new ConstructorBuilder(name)`. -ConstructorBuilder constructorNamed(String name, - [Iterable members = const []]) { +ConstructorBuilder constructorNamed( + String name, [ + Iterable members = const [], +]) { return _constructorImpl(name: name, members: members); } +/// Various types of modifiers for methods. +class MethodModifier implements ValidMethodMember { + static const MethodModifier async = const MethodModifier._('async', false); + static const MethodModifier asyncStar = const MethodModifier._('async', true); + static const MethodModifier syncStar = const MethodModifier._('sync', true); + + final String _keyword; + + const MethodModifier._(this._keyword, this.isStar); + + @override + buildAst([_]) => throw new UnsupportedError('Not an AST'); + + final bool isStar; + + Token keyword() => new StringToken(TokenType.KEYWORD, _keyword, 0); +} + /// Short-hand for `new MethodBuilder.getter(...)`. MethodBuilder getter( String name, { + MethodModifier modifier, Iterable statements, ExpressionBuilder returns, TypeBuilder returnType, @@ -39,12 +61,14 @@ MethodBuilder getter( if (returns != null) { return new MethodBuilder.getter( name, + modifier: modifier, returnType: returnType, returns: returns, ); } else { return new MethodBuilder.getter( name, + modifier: modifier, returnType: returnType, )..addStatements(statements); } @@ -54,9 +78,15 @@ MethodBuilder getter( MethodBuilder lambda( String name, ExpressionBuilder value, { + MethodModifier modifier, TypeBuilder returnType, }) { - return new MethodBuilder(name, returns: value, returnType: returnType); + return new MethodBuilder( + name, + modifier: modifier, + returns: value, + returnType: returnType, + ); } /// A more short-hand way of constructing a [MethodBuilder]. @@ -67,6 +97,7 @@ MethodBuilder method( final List positional = []; final List<_NamedParameterWrapper> named = <_NamedParameterWrapper>[]; final List statements = []; + MethodModifier modifier; TypeBuilder returnType; for (final member in members) { if (member is TypeBuilder) { @@ -77,12 +108,15 @@ MethodBuilder method( named.add(member); } else if (member is StatementBuilder) { statements.add(member); + } else if (member is MethodModifier) { + modifier = member; } else { throw new StateError('Invalid AST type: ${member.runtimeType}'); } } final method = new _MethodBuilderImpl( name, + modifier: modifier, returns: returnType, ); positional.forEach(method.addPositional); @@ -161,8 +195,10 @@ abstract class ConstructorBuilder void addPositional(ParameterBuilder parameter, {bool asField: false}); /// Returns an [ConstructorDeclaration] AST representing the builder. - ConstructorDeclaration buildConstructor(TypeBuilder returnType, - [Scope scope]); + ConstructorDeclaration buildConstructor( + TypeBuilder returnType, [ + Scope scope, + ]); } /// Lazily builds a method/function AST when the builder is invoked. @@ -176,6 +212,7 @@ abstract class MethodBuilder /// Creates a new [MethodBuilder]. factory MethodBuilder( String name, { + MethodModifier modifier, ExpressionBuilder returns, TypeBuilder returnType, }) { @@ -185,10 +222,12 @@ abstract class MethodBuilder returns, returnType, null, + modifier, ); } else { return new _MethodBuilderImpl( name, + modifier: modifier, returns: returnType, ); } @@ -196,6 +235,7 @@ abstract class MethodBuilder /// Creates a new [MethodBuilder] that returns an anonymous closure. factory MethodBuilder.closure({ + MethodModifier modifier, ExpressionBuilder returns, TypeBuilder returnType, }) { @@ -205,10 +245,12 @@ abstract class MethodBuilder returns, returnType, null, + modifier, ); } else { return new _MethodBuilderImpl( null, + modifier: modifier, returns: returnType, ); } @@ -217,12 +259,14 @@ abstract class MethodBuilder /// Creates a getter. factory MethodBuilder.getter( String name, { + MethodModifier modifier, TypeBuilder returnType, ExpressionBuilder returns, }) { if (returns == null) { return new _MethodBuilderImpl( name, + modifier: modifier, returns: returnType, property: Keyword.GET, ); @@ -232,6 +276,7 @@ abstract class MethodBuilder returns, returnType, Keyword.GET, + modifier, ); } } @@ -246,6 +291,7 @@ abstract class MethodBuilder returns, lib$core.$void, null, + null, ); } @@ -265,6 +311,7 @@ abstract class MethodBuilder returns, null, Keyword.SET, + null, ); } } @@ -300,12 +347,18 @@ class _LambdaMethodBuilder extends Object TopLevelMixin implements MethodBuilder { final ExpressionBuilder _expression; + final MethodModifier _modifier; final String _name; final TypeBuilder _returnType; final Keyword _property; _LambdaMethodBuilder( - this._name, this._expression, this._returnType, this._property); + this._name, + this._expression, + this._returnType, + this._property, + this._modifier, + ); @override void addStatement(StatementBuilder statement) { @@ -335,7 +388,7 @@ class _LambdaMethodBuilder extends Object null, _property != Keyword.GET ? buildParameterList(scope) : null, astFactory.expressionFunctionBody( - null, + _modifier?.keyword(), null, _expression.buildExpression(scope), isStatement ? $semicolon : null, @@ -351,7 +404,7 @@ class _LambdaMethodBuilder extends Object null, _returnType?.buildType(scope), _property != null ? new KeywordToken(_property, 0) : null, - stringIdentifier(_name), + _name != null ? stringIdentifier(_name) : null, _buildExpression(scope, isStatement: true), ); } @@ -370,7 +423,7 @@ class _LambdaMethodBuilder extends Object null, _property != Keyword.GET ? buildParameterList(scope) : null, astFactory.expressionFunctionBody( - null, + _modifier?.keyword(), null, _expression.buildExpression(scope), $semicolon, @@ -390,16 +443,19 @@ class _MethodBuilderImpl extends Object HasStatementsMixin, TopLevelMixin implements MethodBuilder { + final MethodModifier _modifier; final String _name; final TypeBuilder _returnType; final Keyword _property; _MethodBuilderImpl( this._name, { + MethodModifier modifier, TypeBuilder returns, Keyword property, }) - : _returnType = returns, + : _modifier = modifier, + _returnType = returns, _property = property; @override @@ -416,8 +472,8 @@ class _MethodBuilderImpl extends Object null, _property != Keyword.GET ? buildParameterList(scope) : null, astFactory.blockFunctionBody( - null, - null, + _modifier?.keyword(), + _modifier?.isStar == true ? $star : null, buildBlock(scope), ), ); @@ -450,8 +506,8 @@ class _MethodBuilderImpl extends Object null, _property != Keyword.GET ? buildParameterList(scope) : null, astFactory.blockFunctionBody( - null, - null, + _modifier?.keyword(), + _modifier?.isStar == true ? $star : null, buildBlock(scope), ), ); diff --git a/pkgs/code_builder/lib/src/builders/reference.dart b/pkgs/code_builder/lib/src/builders/reference.dart index b48f725b9..5e771ad0b 100644 --- a/pkgs/code_builder/lib/src/builders/reference.dart +++ b/pkgs/code_builder/lib/src/builders/reference.dart @@ -6,6 +6,7 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/standard_ast_factory.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/expression.dart'; +import 'package:code_builder/src/builders/file.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/statement.dart'; import 'package:code_builder/src/builders/type.dart'; @@ -63,6 +64,26 @@ class ReferenceBuilder extends Object return new TypeBuilder(_name, importFrom: _importFrom).buildType(scope); } + @override + ExportBuilder toExportBuilder() { + if (_importFrom == null) { + throw new StateError('Cannot create an import - no URI provided'); + } + return new ExportBuilder(_importFrom)..show(_name); + } + + @override + ImportBuilder toImportBuilder({bool deferred: false, String prefix}) { + if (_importFrom == null) { + throw new StateError('Cannot create an import - no URI provided'); + } + return new ImportBuilder( + _importFrom, + deferred: deferred, + prefix: prefix, + )..show(_name); + } + /// Returns a new [ReferenceBuilder] with [genericTypes]. /// /// Example use: diff --git a/pkgs/code_builder/lib/src/builders/type.dart b/pkgs/code_builder/lib/src/builders/type.dart index 27f6b6ad4..851462bc4 100644 --- a/pkgs/code_builder/lib/src/builders/type.dart +++ b/pkgs/code_builder/lib/src/builders/type.dart @@ -8,6 +8,7 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/standard_ast_factory.dart'; import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/code_builder.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/expression.dart'; import 'package:code_builder/src/builders/method.dart'; @@ -62,6 +63,12 @@ abstract class AbstractTypeBuilderMixin { return builder; } + /// Returns as an import to this reference. + ImportBuilder toImportBuilder({bool deferred: false, String prefix}); + + /// Returns as an export to the reference. + ExportBuilder toExportBuilder(); + static void _addArguments( NewInstanceBuilder builder, Iterable positional, @@ -123,4 +130,24 @@ class TypeBuilder extends Object ), ); } + + @override + ExportBuilder toExportBuilder() { + if (_importFrom == null) { + throw new StateError('Cannot create an import - no URI provided'); + } + return new ExportBuilder(_importFrom)..show(_name); + } + + @override + ImportBuilder toImportBuilder({bool deferred: false, String prefix}) { + if (_importFrom == null) { + throw new StateError('Cannot create an import - no URI provided'); + } + return new ImportBuilder( + _importFrom, + deferred: deferred, + prefix: prefix, + )..show(_name); + } } diff --git a/pkgs/code_builder/lib/src/pretty_printer.dart b/pkgs/code_builder/lib/src/pretty_printer.dart index 53d332420..5aef4dc04 100644 --- a/pkgs/code_builder/lib/src/pretty_printer.dart +++ b/pkgs/code_builder/lib/src/pretty_printer.dart @@ -84,7 +84,10 @@ class _PrettyToSourceVisitor extends ToSourceVisitor { // Write a list of [nodes], separated by the given [separator], followed by // the given [suffix] if the list is not empty. void _visitNodeListWithSeparatorAndSuffix( - NodeList nodes, String separator, String suffix) { + NodeList nodes, + String separator, + String suffix, + ) { if (nodes != null) { int size = nodes.length; if (size > 0) { diff --git a/pkgs/code_builder/lib/src/tokens.dart b/pkgs/code_builder/lib/src/tokens.dart index 883e2c26c..01b42da9b 100644 --- a/pkgs/code_builder/lib/src/tokens.dart +++ b/pkgs/code_builder/lib/src/tokens.dart @@ -14,9 +14,15 @@ final Token $as = new KeywordToken(Keyword.AS, 0); /// The `assert` token. final Token $assert = new KeywordToken(Keyword.ASSERT, 0); +/// The `async` token. +final Token $async = new StringToken(TokenType.KEYWORD, 'async', 0); + /// The `@` token. final Token $at = new Token(TokenType.AT, 0); +/// The `await` token. +final Token $await = new StringToken(TokenType.KEYWORD, 'await', 0); + /// The `class` token. final Token $class = new KeywordToken(Keyword.CLASS, 0); @@ -65,11 +71,17 @@ final Token $gt = new Token(TokenType.GT, 0); /// The `if` token. final Token $if = new KeywordToken(Keyword.IF, 0); +/// The `yield` token. +final Token $yield = new StringToken(TokenType.KEYWORD, 'yield', 0); + // Simple tokens /// The `&&` token. final Token $and = new Token(TokenType.AMPERSAND_AMPERSAND, 0); +/// The `*` token. +final Token $star = $multiply; + /// The `hide` token. final Token $hide = new StringToken(TokenType.KEYWORD, 'hide', 0); diff --git a/pkgs/code_builder/pubspec.yaml b/pkgs/code_builder/pubspec.yaml index de1f53f5f..5b84babd5 100644 --- a/pkgs/code_builder/pubspec.yaml +++ b/pkgs/code_builder/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 1.0.0-alpha+8 +version: 1.0.0-alpha+9 description: A fluent API for generating Dart code author: Dart Team homepage: https://github.com/dart-lang/code_builder diff --git a/pkgs/code_builder/test/builders/expression_test.dart b/pkgs/code_builder/test/builders/expression_test.dart index 2fa687bb9..36f477ea5 100644 --- a/pkgs/code_builder/test/builders/expression_test.dart +++ b/pkgs/code_builder/test/builders/expression_test.dart @@ -294,4 +294,31 @@ void main() { '''), ); }); + + test('should emit await', () { + expect( + reference('foo').asAwait(), + equalsSource(r''' + await foo + '''), + ); + }); + + test('should emit yield', () { + expect( + reference('foo').asYield(), + equalsSource(r''' + yield foo; + '''), + ); + }); + + test('should emit yield*', () { + expect( + reference('foo').asYieldStar(), + equalsSource(r''' + yield* foo; + '''), + ); + }); } diff --git a/pkgs/code_builder/test/builders/method_test.dart b/pkgs/code_builder/test/builders/method_test.dart index 332cdf913..5ff6c7ad0 100644 --- a/pkgs/code_builder/test/builders/method_test.dart +++ b/pkgs/code_builder/test/builders/method_test.dart @@ -170,6 +170,33 @@ void main() { ); }); + test('should emit an async method', () { + expect( + method('fetch', [MethodModifier.async]), + equalsSource(r''' + fetch() async {} + '''), + ); + }); + + test('should emit an async* method', () { + expect( + method('fetch', [MethodModifier.asyncStar]), + equalsSource(r''' + fetch() async* {} + '''), + ); + }); + + test('should emit an sync* method', () { + expect( + method('fetch', [MethodModifier.syncStar]), + equalsSource(r''' + fetch() sync* {} + '''), + ); + }); + group('closure', () { MethodBuilder closure; setUp(() { @@ -185,22 +212,26 @@ void main() { expect( closure, equalsSource(r''' - (bool defaultTo) => false || defaultTo - '''), + (bool defaultTo) => false || defaultTo + '''), ); test('should treat closure as expression', () { expect( - list([true, false]).invoke('where', [closure]), - equalsSource( - '[true, false].where((bool defaultTo) => false || defaultTo)')); + list([true, false]).invoke('where', [closure]), + equalsSource( + '[true, false].where((bool defaultTo) => false || defaultTo)', + ), + ); }); test('should emit a closure as a function in a library', () { final library = new LibraryBuilder(); library.addMember(closure); expect( - library, equalsSource('(bool defaultTo) => false || defaultTo;')); + library, + equalsSource('(bool defaultTo) => false || defaultTo;'), + ); }); }); }); diff --git a/pkgs/code_builder/test/builders/type_test.dart b/pkgs/code_builder/test/builders/type_test.dart index bd82af37b..16c9f4d70 100644 --- a/pkgs/code_builder/test/builders/type_test.dart +++ b/pkgs/code_builder/test/builders/type_test.dart @@ -94,5 +94,23 @@ void main() { '''), ); }); + + test('emits an export builder', () { + expect( + reference('Foo', 'package:foo/foo.dart').toExportBuilder(), + equalsSource(r''' + export 'package:foo/foo.dart' show Foo; + '''), + ); + }); + + test('emits an export builder', () { + expect( + reference('Foo', 'package:foo/foo.dart').toImportBuilder(), + equalsSource(r''' + import 'package:foo/foo.dart' show Foo; + '''), + ); + }); }); }