diff --git a/CHANGELOG.md b/CHANGELOG.md index 381b2aa..32c4fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ This should help cover any cases not covered with builders today. +- Allow referring to a `ClassBuilder` and `TypeBuilder` as an expression +- Add support for accessing the index `[]` operator on an expression + +### BREAKING CHANGES + +- Changed `ExpressionBuilder.asAssign` to always take an `ExpressionBuilder` as + target and removed the `value` property. Most changes are pretty simple, and + involve just using `reference(...)`. For example: + +```dart +literal(true).asAssign(reference('flag')) +``` + +... emits `flag = true`. + ## 1.0.0-beta - Add support for `async`, `sync`, `sync*` functions diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index ea1794c..9217ab5 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -6,10 +6,13 @@ import 'package:analyzer/analyzer.dart'; 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/expression.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/reference.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'; import 'package:code_builder/src/tokens.dart'; @@ -133,7 +136,7 @@ abstract class ClassBuilder abstract class ValidClassMember implements AstBuilder {} class _ClassBuilderImpl extends Object - with AbstractTypeBuilderMixin, HasAnnotationsMixin + with AbstractExpressionMixin, AbstractTypeBuilderMixin, HasAnnotationsMixin implements ClassBuilder { final _constructors = []; final _fields = {}; @@ -265,6 +268,14 @@ class _ClassBuilderImpl extends Object ImportBuilder toImportBuilder({bool deferred: false, String prefix}) { throw new UnsupportedError('Not supported for ClassBuilder'); } + + @override + Expression buildExpression([Scope scope]) { + return reference(_name).buildExpression(scope); + } + + @override + CompilationUnitMember buildTopLevelAst([Scope scope]) => buildClass(scope); } class _TypeNameWrapper implements ValidClassMember { diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 5e086e2..b63fe06 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -23,6 +23,7 @@ part 'expression/assert.dart'; part 'expression/assign.dart'; part 'expression/await.dart'; part 'expression/cascade.dart'; +part 'expression/index.dart'; part 'expression/invocation.dart'; part 'expression/negate.dart'; part 'expression/operators.dart'; @@ -146,6 +147,11 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { ); } + @override + ExpressionBuilder operator [](ExpressionBuilder index) { + return new _IndexExpression(this, index); + } + @override ExpressionBuilder and(ExpressionBuilder other) { return new _AsBinaryExpression( @@ -160,11 +166,10 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { @override StatementBuilder asAssign( - String variable, { - ExpressionBuilder target, + ExpressionBuilder target, { bool nullAware: false, }) => - new _AsAssign(this, variable, nullAware, target); + new _AsAssign(this, nullAware, target); @override ExpressionBuilder asAwait() => new _AsAwait(this); @@ -320,18 +325,18 @@ abstract class ExpressionBuilder /// Returns as an [ExpressionBuilder] `>` by [other]. ExpressionBuilder operator >(ExpressionBuilder other); + /// Returns as an [ExpressionBuilder] reading the `[]` property with [index]. + ExpressionBuilder operator [](ExpressionBuilder index); + /// Returns as an [ExpressionBuilder] `&&` [other]. ExpressionBuilder and(ExpressionBuilder other); /// Return as a [StatementBuilder] that `assert`s this expression. StatementBuilder asAssert(); - /// Returns as a [StatementBuilder] that assigns to an existing [variable]. - /// - /// If [target] is specified, determined to be `{target}.{variable}`. + /// Returns as a [StatementBuilder] that assigns to [target]. StatementBuilder asAssign( - String variable, { - ExpressionBuilder target, + ExpressionBuilder target, { bool nullAware, }); diff --git a/lib/src/builders/expression/assign.dart b/lib/src/builders/expression/assign.dart index 2f9c72a..20b4dc9 100644 --- a/lib/src/builders/expression/assign.dart +++ b/lib/src/builders/expression/assign.dart @@ -5,12 +5,11 @@ part of code_builder.src.builders.expression; class _AsAssign extends AbstractExpressionMixin with TopLevelMixin { - final String _name; final bool _nullAware; final ExpressionBuilder _value; final ExpressionBuilder _target; - _AsAssign(this._value, this._name, this._nullAware, this._target); + _AsAssign(this._value, this._nullAware, this._target); @override AstNode buildAst([Scope scope]) => buildExpression(scope); @@ -18,9 +17,7 @@ class _AsAssign extends AbstractExpressionMixin with TopLevelMixin { @override Expression buildExpression([Scope scope]) { return astFactory.assignmentExpression( - _target != null - ? _target.property(_name).buildExpression(scope) - : stringIdentifier(_name), + _target.buildExpression(scope), _nullAware ? $nullAwareEquals : $equals, _value.buildExpression(scope), ); diff --git a/lib/src/builders/expression/await.dart b/lib/src/builders/expression/await.dart index 5b15fab..011cf45 100644 --- a/lib/src/builders/expression/await.dart +++ b/lib/src/builders/expression/await.dart @@ -14,7 +14,7 @@ class _AsAwait extends AbstractExpressionMixin with TopLevelMixin { @override Expression buildExpression([Scope scope]) { - return new AwaitExpression( + return astFactory.awaitExpression( $await, _expression.buildExpression(scope), ); diff --git a/lib/src/builders/expression/index.dart b/lib/src/builders/expression/index.dart new file mode 100644 index 0000000..2591e2c --- /dev/null +++ b/lib/src/builders/expression/index.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 _IndexExpression extends AbstractExpressionMixin with TopLevelMixin { + final ExpressionBuilder _target; + final ExpressionBuilder _index; + + _IndexExpression(this._target, this._index); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return astFactory.indexExpressionForTarget( + _target.buildExpression(scope), + $openBracket, + _index.buildExpression(scope), + $closeBracket, + ); + } +} diff --git a/lib/src/builders/expression/yield.dart b/lib/src/builders/expression/yield.dart index 9874261..decbcfb 100644 --- a/lib/src/builders/expression/yield.dart +++ b/lib/src/builders/expression/yield.dart @@ -15,7 +15,7 @@ class _AsYield extends TopLevelMixin implements StatementBuilder { @override Statement buildStatement([Scope scope]) { - return new YieldStatement( + return astFactory.yieldStatement( $yield, _isStar ? $star : null, _expression.buildExpression(scope), diff --git a/lib/src/builders/statement/switch.dart b/lib/src/builders/statement/switch.dart index e2bc1bf..f8e49cb 100644 --- a/lib/src/builders/statement/switch.dart +++ b/lib/src/builders/statement/switch.dart @@ -10,28 +10,35 @@ import 'package:code_builder/src/builders/statement.dart'; import 'package:code_builder/src/tokens.dart'; /// Short-hand syntax for `new SwitchCaseBuilder(...)`. -SwitchCaseBuilder switchCase(ExpressionBuilder condition, - [Iterable statements = const []]) => +SwitchCaseBuilder switchCase( + ExpressionBuilder condition, [ + Iterable statements = const [], +]) => new SwitchCaseBuilder(condition)..addStatements(statements ?? []); /// Short-hand syntax for `new SwitchDefaultCaseBuilder(...)`. -SwitchDefaultCaseBuilder switchDefault( - [Iterable statements = const []]) => +SwitchDefaultCaseBuilder switchDefault([ + Iterable statements = const [], +]) => new SwitchDefaultCaseBuilder()..addStatements(statements ?? []); /// Short-hand syntax for `new SwitchStatementBuilder(...)`. -SwitchStatementBuilder switchStatement(ExpressionBuilder expression, - {Iterable cases: const [], - SwitchDefaultCaseBuilder defaultCase}) => +SwitchStatementBuilder switchStatement( + ExpressionBuilder expression, { + Iterable cases: const [], + SwitchDefaultCaseBuilder defaultCase, +}) => new SwitchStatementBuilder(expression) ..setDefaultCase(defaultCase) ..addCases(cases ?? []); -/// Represents an [ExpressionBuilder] and a series of [SwitchCaseBuilder]s as a switch statement AST. +/// Represents an [ExpressionBuilder] switches as an AST. abstract class SwitchStatementBuilder implements StatementBuilder { /// Creates a new [SwitchStatementBuilder]. - factory SwitchStatementBuilder(ExpressionBuilder expression, - [Iterable members = const []]) => + factory SwitchStatementBuilder( + ExpressionBuilder expression, [ + Iterable members = const [], + ]) => new _SwitchStatementBuilder(expression, members); /// Adds a [switchCase] to the builder. @@ -47,16 +54,15 @@ abstract class SwitchStatementBuilder implements StatementBuilder { void setDefaultCase(SwitchDefaultCaseBuilder defaultCase); } -/// A marker interface for an AST that could be added to [SwitchStatementBuilder]. +/// A marker interface for an AST usable within a [SwitchStatementBuilder]. /// /// This can be either a [SwitchCaseBuilder] or a [SwitchDefaultCaseBuilder]. abstract class ValidSwitchMember {} -/// Represents an [ExpressionBuilder] and a series of [Statement]s as a switch case AST. +/// Represents an [ExpressionBuilder] and statements as a switch case AST. abstract class SwitchCaseBuilder implements HasStatements, ValidSwitchMember { /// Creates a new [SwitchCaseBuilder]. - factory SwitchCaseBuilder(ExpressionBuilder condition) => - new _SwitchCaseBuilder(condition); + factory SwitchCaseBuilder(ExpressionBuilder condition) = _SwitchCaseBuilder; /// Returns an [SwitchMember] AST representing the builder. SwitchMember buildSwitchMember([Scope scope]); @@ -65,7 +71,7 @@ abstract class SwitchCaseBuilder implements HasStatements, ValidSwitchMember { /// Represents a series of [Statement]s as a default switch case AST. abstract class SwitchDefaultCaseBuilder implements HasStatements, ValidSwitchMember { - factory SwitchDefaultCaseBuilder() => new _SwitchDefaultCaseBuilder(); + factory SwitchDefaultCaseBuilder() = _SwitchDefaultCaseBuilder; /// Returns an [SwitchMember] AST representing the builder. SwitchMember buildSwitchMember([Scope scope]); @@ -78,12 +84,16 @@ class _SwitchStatementBuilder extends Object final List _cases = []; SwitchDefaultCaseBuilder _defaultCase; - _SwitchStatementBuilder(this._expression, - [Iterable members = const []]) { + _SwitchStatementBuilder( + this._expression, [ + Iterable members = const [], + ]) { for (final member in members) { - if (member is SwitchDefaultCaseBuilder) + if (member is SwitchDefaultCaseBuilder) { _defaultCase = member; - else if (member is SwitchCaseBuilder) _cases.add(member); + } else if (member is SwitchCaseBuilder) { + _cases.add(member); + } } } @@ -105,18 +115,18 @@ class _SwitchStatementBuilder extends Object @override SwitchStatement buildSwitchStatement([Scope scope]) { var members = _cases.map((c) => c.buildSwitchMember(scope)).toList(); - - if (_defaultCase != null) + if (_defaultCase != null) { members.add(_defaultCase.buildSwitchMember(scope)); - + } return astFactory.switchStatement( - $switch, - $openBracket, - _expression.buildExpression(), - $closeParen, - $openBracket, - members, - $closeBracket); + $switch, + $openBracket, + _expression.buildExpression(), + $closeParen, + $openBracket, + members, + $closeBracket, + ); } @override @@ -134,8 +144,13 @@ class _SwitchCaseBuilder extends Object AstNode buildAst([Scope scope]) => buildSwitchMember(scope); @override - SwitchMember buildSwitchMember([Scope scope]) => astFactory.switchCase(null, - $case, _condition.buildExpression(), $colon, buildStatements(scope)); + SwitchMember buildSwitchMember([Scope scope]) => astFactory.switchCase( + null, + $case, + _condition.buildExpression(), + $colon, + buildStatements(scope), + ); } class _SwitchDefaultCaseBuilder extends Object @@ -145,6 +160,10 @@ class _SwitchDefaultCaseBuilder extends Object AstNode buildAst([Scope scope]) => buildSwitchMember(scope); @override - SwitchMember buildSwitchMember([Scope scope]) => - astFactory.switchDefault(null, $default, $colon, buildStatements()); + SwitchMember buildSwitchMember([Scope scope]) => astFactory.switchDefault( + null, + $default, + $colon, + buildStatements(), + ); } diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart index 851462b..7653c4c 100644 --- a/lib/src/builders/type.dart +++ b/lib/src/builders/type.dart @@ -81,8 +81,12 @@ abstract class AbstractTypeBuilderMixin { /// Lazily builds an [TypeName] AST when [buildType] is invoked. class TypeBuilder extends Object - with AbstractTypeBuilderMixin - implements AstBuilder, ValidMethodMember, ValidParameterMember { + with AbstractExpressionMixin, AbstractTypeBuilderMixin, TopLevelMixin + implements + AstBuilder, + ExpressionBuilder, + ValidMethodMember, + ValidParameterMember { final List _generics; final String _importFrom; final String _name; @@ -150,4 +154,12 @@ class TypeBuilder extends Object prefix: prefix, )..show(_name); } + + @override + Expression buildExpression([Scope scope]) { + if (_generics.isNotEmpty) { + throw new StateError('Cannot refer to a type with generic parameters'); + } + return reference(_name, _importFrom).buildExpression(scope); + } } diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart index 0eae4bb..99c6670 100644 --- a/test/builders/expression_test.dart +++ b/test/builders/expression_test.dart @@ -76,7 +76,7 @@ void main() { test('should emit an assign expression', () { expect( - literal(true).asAssign('flag'), + literal(true).asAssign(reference('flag')), equalsSource(r''' flag = true '''), @@ -85,7 +85,7 @@ void main() { test('should emit an assign expression with a null aware', () { expect( - literal(true).asAssign('flag', nullAware: true), + literal(true).asAssign(reference('flag'), nullAware: true), equalsSource(r''' flag ??= true '''), @@ -268,6 +268,24 @@ void main() { ); }); + test('should return as an index expression', () { + expect( + literal([literal(1), literal(2), literal(3)])[literal(0)], + equalsSource(r''' + [1, 2, 3][0] + '''), + ); + }); + + test('should return as an index expression assignment', () { + expect( + reference('value').asAssign(reference('list')[reference('index')]), + equalsSource(r''' + list[index] = value + '''), + ); + }); + test('should emit a top-level field declaration', () { expect( new LibraryBuilder()..addMember(literal(false).asConst('foo')), @@ -283,7 +301,7 @@ void main() { .cascade((c) => [ c.invoke('doThis', []), c.invoke('doThat', []), - reference('Bar').newInstance([]).asAssign('bar', target: c), + reference('Bar').newInstance([]).asAssign(c.property('bar')), ]) .asStatement(), equalsSource(r''' diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart index 8a6c7b9..3c2836f 100644 --- a/test/builders/method_test.dart +++ b/test/builders/method_test.dart @@ -160,7 +160,7 @@ void main() { test('should emit a setter', () { expect( setter('name', parameter('name', [lib$core.String]), [ - (reference('name') + literal('!')).asAssign('_name'), + (reference('name') + literal('!')).asAssign(reference('_name')), ]), equalsSource(r''' set name(String name) { @@ -241,7 +241,7 @@ void main() { ..addStatement(reference('value').invoke( 'join', [literal(' - ')], - ).asAssign('title'))) + ).asAssign(reference('title')))) .asStatement(), equalsSource(r''' (value) { diff --git a/test/builders/type_test.dart b/test/builders/type_test.dart index 16c9f4d..436edf0 100644 --- a/test/builders/type_test.dart +++ b/test/builders/type_test.dart @@ -113,4 +113,22 @@ void main() { ); }); }); + + test('emits as a Type to be used as an expression', () { + expect( + lambda('foo', new TypeBuilder('Foo')), + equalsSource(r''' + foo() => Foo; + '''), + ); + }); + + test('throws when a Type references generics for an expression', () { + expect( + () => new TypeBuilder('Foo', genericTypes: [ + new TypeBuilder('Bar'), + ]).buildExpression(), + throwsStateError, + ); + }); }