From 54810767031cdc8607828484e6218bc934f0586b Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sun, 9 Oct 2016 16:45:03 -0700 Subject: [PATCH 01/19] Branch to start v1, a partial rewrite. --- analysis_options.yaml | 2 +- lib/code_builder.dart | 11 +- lib/src/builders/constructor_builder.dart | 51 ++-- lib/src/builders/expression/expression.dart | 275 ++++++++++++++++++++ lib/src/builders/expression/operators.dart | 24 ++ lib/src/builders/expression_builder.dart | 107 ++++++-- lib/src/builders/method_builder.dart | 3 + lib/src/builders/statement/statement.dart | 4 + lib/src/builders/statement_builder.dart | 64 +++++ lib/src/builders/type_builder.dart | 2 +- lib/src/pretty_printer.dart | 7 +- lib/src/scope.dart | 6 +- lib/src/tokens.dart | 10 +- lib/testing/equals_source.dart | 51 ++-- pubspec.yaml | 2 +- test/builders/method_builder_test.dart | 17 ++ test/expression_test.dart | 72 +++++ 17 files changed, 646 insertions(+), 62 deletions(-) create mode 100644 lib/src/builders/expression/expression.dart create mode 100644 lib/src/builders/expression/operators.dart create mode 100644 lib/src/builders/statement/statement.dart create mode 100644 test/expression_test.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index f7145db..8ea35ea 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -25,7 +25,7 @@ linter: - empty_constructor_bodies - library_names - library_prefixes - - non_constant_identifier_names + # - non_constant_identifier_names - only_throw_errors - overridden_fields - package_api_docs diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 2df5a4d..aaef5f3 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -40,6 +40,7 @@ part 'src/builders/annotation_builder.dart'; part 'src/builders/class_builder.dart'; part 'src/builders/constructor_builder.dart'; part 'src/builders/expression_builder.dart'; +part 'src/builders/expression/operators.dart'; part 'src/builders/field_builder.dart'; part 'src/builders/file_builder.dart'; part 'src/builders/method_builder.dart'; @@ -54,7 +55,13 @@ final DartFormatter _dartfmt = new DartFormatter(); // Simplifies some of the builders by having a mutable node we clone from. /// Returns [source] formatted by `dartfmt`. @visibleForTesting -String dartfmt(String source) => _dartfmt.format(source); +String dartfmt(String source) { + try { + return _dartfmt.format(source); + } on FormatterException catch (_) { + return _dartfmt.formatStatement(source); + } +} SimpleIdentifier _stringIdentifier(String s) => new SimpleIdentifier(stringToken(s)); @@ -66,5 +73,5 @@ abstract class CodeBuilder { /// Returns a copy-safe [AstNode] representing the current builder state. /// /// Uses [scope] to output an AST re-written to use appropriate prefixes. - A toAst([Scope scope = const Scope.identity()]); + A build([Scope scope = Scope.identity]); } diff --git a/lib/src/builders/constructor_builder.dart b/lib/src/builders/constructor_builder.dart index 8758d8b..33b3e5e 100644 --- a/lib/src/builders/constructor_builder.dart +++ b/lib/src/builders/constructor_builder.dart @@ -10,7 +10,10 @@ part of code_builder; class ConstructorBuilder implements CodeBuilder { final bool _isConstant; final String _name; + + final List _annotations = []; final List _parameters = []; + final List _statements = []; /// Create a new builder for a constructor, optionally with a [name]. factory ConstructorBuilder([String name]) { @@ -26,6 +29,13 @@ class ConstructorBuilder implements CodeBuilder { ConstructorBuilder._(this._isConstant, this._name); + /// Lazily adds [annotation]. + /// + /// When the method is emitted as an AST, [AnnotationBuilder.toAst] is used. + void addAnnotation(AnnotationBuilder annotation) { + _annotations.add(annotation); + } + /// Lazily adds [builder]. /// /// When the method is emitted as an AST, [ParameterBuilder.toAst] is used. @@ -33,25 +43,34 @@ class ConstructorBuilder implements CodeBuilder { _parameters.add(builder); } + /// Lazily adds [statement]. + /// + /// When the method is emitted as an AST, [StatementBuilder.toAst] is used. + void addStatement(StatementBuilder statement) { + _statements.add(statement); + } + @override ConstructorDeclaration toAst([Scope scope = const Scope.identity()]) { var astNode = new ConstructorDeclaration( - null, - null, - null, - null, - _isConstant ? $const : null, - null, - null, - _name != null ? _stringIdentifier(_name) : null, - MethodBuilder._emptyParameters() - ..parameters.addAll( - _parameters.map/**/((p) => p.toAst(scope))), - null, - null, - null, - new EmptyFunctionBody($semicolon), - ); + null, + _annotations.map/**/((a) => a.toAst(scope)), + null, + null, + _isConstant ? $const : null, + null, + null, + _name != null ? _stringIdentifier(_name) : null, + MethodBuilder._emptyParameters() + ..parameters.addAll( + _parameters.map/**/((p) => p.toAst(scope))), + null, + null, + null, + _statements.isEmpty + ? new EmptyFunctionBody($semicolon) + : MethodBuilder._blockBody( + _statements.map/**/((s) => s.toAst(scope)))); return astNode; } } diff --git a/lib/src/builders/expression/expression.dart b/lib/src/builders/expression/expression.dart new file mode 100644 index 0000000..9f2ecc2 --- /dev/null +++ b/lib/src/builders/expression/expression.dart @@ -0,0 +1,275 @@ +import 'package:analyzer/analyzer.dart' + show + AssertStatement, + AssignmentExpression, + BooleanLiteral, + Expression, + ExpressionStatement, + Literal, + NullLiteral, + SimpleIdentifier, + SimpleStringLiteral, + Statement, + VariableDeclaration, + VariableDeclarationList, + VariableDeclarationStatement; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:code_builder/code_builder.dart' + show CodeBuilder, Scope, TypeBuilder; +import 'package:code_builder/src/tokens.dart' + show + $assert, + $closeParen, + $const, + $equals, + $false, + $final, + $null, + $openParen, + $semicolon, + $true, + $var, + stringToken; +import 'package:code_builder/src/builders/statement/statement.dart' + show StatementBuilder; + +final Literal _null = new NullLiteral($null); +final Literal _true = new BooleanLiteral($true, true); +final Literal _false = new BooleanLiteral($false, false); + +final ExpressionBuilder _literalNull = new _LiteralExpression(_null); +final ExpressionBuilder _literalTrue = new _LiteralExpression(_true); +final ExpressionBuilder _literalFalse = new _LiteralExpression(_false); + +/// Return a literal [value]. +/// +/// Must either be `null` or [bool], [num], or [String]. +/// +/// __Example use__: +/// literal(null); +/// literal('Hello World'); +/// literal(5); +/// literal(false); +/// +/// **NOTE**: A string literal is automatically wrapped in single-quotes. +/// +/// TODO(matanl): Add support for literal [List] and [Map] of other literals. +ExpressionBuilder literal(value) { + if (value == null) { + return _literalNull; + } + if (value is bool) { + return value ? _literalTrue : _literalFalse; + } + if (value is String) { + return new _LiteralExpression(new SimpleStringLiteral( + stringToken("'$value'"), + value, + )); + } + if (value is num) {} + throw new ArgumentError('Invalid type: ${value.runtimeType}'); +} + +/// Builds an [Expression] AST. +abstract class ExpressionBuilder implements CodeBuilder { + /// Returns _as_ a statement that `asserts` the expression. + StatementBuilder asAssert(); + + /// Returns _as_ an expression that assigns to an existing [variable]. + /// + /// __Example use__: + /// literalTrue.assign('foo') // Outputs "foo = true" + ExpressionBuilder assign(String variable); + + /// Returns _as_ a statement that assigns to a new `const` [variable]. + /// + /// __Example use__: + /// literalTrue.asConst('foo') // Outputs "const foo = true;" + StatementBuilder asConst(String variable, [TypeBuilder type]); + + /// Returns _as_ a statement that assigns to a new `final` [variable]. + /// + /// __Example use__: + /// literalTrue.asFinal('foo') // Outputs "final foo = true;" + StatementBuilder asFinal(String variable, [TypeBuilder type]); + + /// Returns _as_ a statement that assigns to a new [variable]. + /// + /// __Example use__: + /// literalTrue.asVar('foo') // Outputs "var foo = true;" + StatementBuilder asVar(String variable); + + /// Returns _as_ a statement that builds an `if` statement. + /// + /// __Example use__: + /// literalTrue.asIf() // Outputs "if (true) { ... }" + /*If*/ StatementBuilder asIf(); + + /// Returns _as_ a statement that `return`s this expression. + /// + /// __Example use__: + /// literalTrue.asReturn() // Outputs "return true;" + StatementBuilder asReturn(); + + /// Returns _as_ a statement. + /// + /// An expression itself in Dart is a valid statement. + /// + /// __Example use__: + /// invoke('foo').asStatement() // Outputs "foo();" + StatementBuilder asStatement(); + + /// Returns _as_ an expression using the equality operator on [condition]. + /// + /// __Example use__: + /// literalTrue.equals(literalTrue) // Outputs "true == true" + ExpressionBuilder equals(ExpressionBuilder condition); + + /// Returns _as_ an expression using the not-equals operator on [condition]. + /// + /// __Example use__: + /// literalTrue.notEquals(literalTrue) // Outputs "true != true" + ExpressionBuilder notEquals(ExpressionBuilder condition); + + /// Returns _as_ an expression negating this one. + /// + /// __Example use__: + /// literalTrue.not() // Outputs "!true" + ExpressionBuilder not(); + + /// Returns _as_ an expression wrapped in parentheses. + /// + /// __Example use__: + /// literalTrue.parentheses() // Outputs "(true)" + ExpressionBuilder parentheses(); +} + +/// A partial implementation of [ExpressionBuilder] suitable as a mixin. +abstract class ExpressionBuilderMixin implements ExpressionBuilder { + @override + StatementBuilder asAssert() => new _AsAssert(this); + + @override + ExpressionBuilder assign(String variable) => new _AsAssign(this, variable); + + @override + StatementBuilder asConst(String variable, [TypeBuilder type]) => + new _AsAssignStatement(this, variable, + keyword: _AsAssignType.asConst, type: type); + + @override + StatementBuilder asFinal(String variable, [TypeBuilder type]) => + new _AsAssignStatement(this, variable, + keyword: _AsAssignType.asFinal, type: type); + + @override + StatementBuilder asVar(String variable, [TypeBuilder type]) => + new _AsAssignStatement(this, variable, + keyword: _AsAssignType.asVar, type: type); + + @override + StatementBuilder asStatement() => new _AsStatement(this); +} + +class _AsAssert implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsAssert(this._expression); + + @override + Statement build([Scope scope = Scope.identity]) { + return new AssertStatement( + $assert, + $openParen, + _expression.build(scope), + null, + null, + $closeParen, + $semicolon, + ); + } +} + +class _AsAssign extends ExpressionBuilderMixin implements ExpressionBuilder { + final ExpressionBuilder _expression; + final String _name; + + _AsAssign(this._expression, this._name); + + @override + Expression build([Scope scope = Scope.identity]) { + final name = new SimpleIdentifier(stringToken(_name)); + return new AssignmentExpression( + name, + $equals, + _expression.build(scope), + ); + } +} + +class _AsAssignStatement implements StatementBuilder { + final ExpressionBuilder _expression; + final String _name; + final _AsAssignType _keyword; + final TypeBuilder _type; + + _AsAssignStatement( + this._expression, + this._name, { + _AsAssignType keyword, + TypeBuilder type, + }) + : _keyword = keyword, + _type = type; + + @override + Statement build([Scope scope = Scope.identity]) { + final name = new SimpleIdentifier(stringToken(_name)); + Token token; + switch (_keyword) { + case _AsAssignType.asConst: + token = $const; + break; + case _AsAssignType.asFinal: + token = $final; + break; + case _AsAssignType.asVar: + token = $var; + break; + } + return new VariableDeclarationStatement( + new VariableDeclarationList( + null, + null, + token, + _type?.build(scope), + [new VariableDeclaration(name, $equals, _expression.build(scope))], + ), + $semicolon, + ); + } +} + +class _AsStatement implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsStatement(this._expression); + + @override + Statement build([Scope scope = Scope.identity]) { + return new ExpressionStatement(_expression.build(scope), $semicolon); + } +} + +class _LiteralExpression extends ExpressionBuilderMixin + implements CodeBuilder { + final Literal _literal; + + _LiteralExpression(this._literal); + + @override + Literal build([_]) => _literal; +} + +enum _AsAssignType { asConst, asFinal, asVar, } diff --git a/lib/src/builders/expression/operators.dart b/lib/src/builders/expression/operators.dart new file mode 100644 index 0000000..4b8a416 --- /dev/null +++ b/lib/src/builders/expression/operators.dart @@ -0,0 +1,24 @@ +// 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; + +class _BinaryExpression extends _ExpressionBase { + final ExpressionBuilder _left; + final ExpressionBuilder _right; + final Token _operator; + + _OperatorExpression.equals(this._a, this._b) : _operator = $equals; + + _OperatorExpression.notEquals(this._a, this._b) : _operator = $notEquals; + + @override + Expression toAst([Scope scope = const Scope.identity()]) { + return new BinaryExpression( + _left.toAst(scope), + _operator, + _right.toAst(scope), + ); + } +} diff --git a/lib/src/builders/expression_builder.dart b/lib/src/builders/expression_builder.dart index 7175c8c..50b7bdb 100644 --- a/lib/src/builders/expression_builder.dart +++ b/lib/src/builders/expression_builder.dart @@ -120,15 +120,27 @@ abstract class ExpressionBuilder implements CodeBuilder { Map> named: const {}, }); // TODO(matanl): Rename to invoke when factory is removed. + /// Returns a new statement of `assert(expression)`. + StatementBuilder asAssert(); + /// Returns wrapped as a [ExpressionFunctionBody] AST. - ExpressionFunctionBody toFunctionBody( - [Scope scope = const Scope.identity()]) => - _asFunctionBody(this, scope); + ExpressionFunctionBody toFunctionBody([Scope scope = const Scope.identity()]); /// Returns wrapped as a [FunctionExpression] AST. FunctionExpression toFunctionExpression( - [Scope scope = const Scope.identity()]) => - _asFunctionExpression(this, scope); + [Scope scope = const Scope.identity()]); + + /// Returns as an `if () {' builder. + IfStatementBuilder asIfStatement(); + + /// Converts to a `return `. + StatementBuilder asReturnStatement(); + + /// Returns as an ` == ` expression. + ExpressionBuilder asEquals(ExpressionBuilder other); + + /// Returns as an ` != ` expression. + ExpressionBuilder asNotEquals(ExpressionBuilder other); /// Converts to a [StatementBuilder]. /// @@ -137,6 +149,29 @@ abstract class ExpressionBuilder implements CodeBuilder { StatementBuilder toStatement(); } +abstract class _ExpressionBase implements ExpressionBuilder { + const _ExpressionBase(); + + @override + StatementBuilder asAssert() => new _AssertionStatementBuilder(this); + + @override + ExpressionFunctionBody toFunctionBody( + [Scope scope = const Scope.identity()]) => + _asFunctionBody(this, scope); + + @override + FunctionExpression toFunctionExpression( + [Scope scope = const Scope.identity()]) => + _asFunctionExpression(this, scope); + + @override + IfStatementBuilder asIfStatement() => new IfStatementBuilder._(this); + + @override + StatementBuilder asReturnStatement() => new _ReturnStatementBuilder(this); +} + /// Creates a new literal `bool` value. class LiteralBool extends _LiteralExpression { static final BooleanLiteral _true = new BooleanLiteral($true, true); @@ -179,7 +214,7 @@ class LiteralString extends _LiteralExpression { ); } -class _InvokeExpression extends ExpressionBuilder { +class _InvokeExpression extends _ExpressionBase { final String _importFrom; final ExpressionBuilder _target; final String _name; @@ -194,8 +229,7 @@ class _InvokeExpression extends ExpressionBuilder { this._importFrom, ) : _target = null, - _type = null, - super._(); + _type = null; const _InvokeExpression.newInstance( this._type, @@ -204,14 +238,12 @@ class _InvokeExpression extends ExpressionBuilder { this._namedArguments, ) : _target = null, - _importFrom = null, - super._(); + _importFrom = null; const _InvokeExpression.target( this._name, this._target, this._positionalArguments, this._namedArguments) : _importFrom = null, - _type = null, - super._(); + _type = null; @override ExpressionBuilder invokeSelf( @@ -264,13 +296,12 @@ class _InvokeExpression extends ExpressionBuilder { } } -class _AssignmentExpression extends ExpressionBuilder { +class _AssignmentExpression extends _ExpressionBase { final String left; final CodeBuilder right; final bool nullAware; - _AssignmentExpression(this.left, this.right, {this.nullAware: false}) - : super._(); + _AssignmentExpression(this.left, this.right, {this.nullAware: false}); @override ExpressionBuilder invokeSelf(String name, @@ -289,7 +320,50 @@ class _AssignmentExpression extends ExpressionBuilder { StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); } +class _ClosureExpressionBuilder extends _ExpressionBase { + final MethodBuilder _method; + + _ClosureExpressionBuilder(this._method); + + @override + ExpressionBuilder invokeSelf(String name, + {Iterable> positional: const [], + Map> named: const {}}) { + return _invokeSelfImpl(this, name, positional: positional, named: named); + } + + @override + Expression toAst([Scope scope = const Scope.identity()]) { + final expression = new FunctionExpression( + null, + MethodBuilder._emptyParameters(), + null, + ); + if (_method._returnExpression != null) { + return expression..body = new ExpressionFunctionBody( + null, + null, + _method._returnExpression.toAst(scope), + $semicolon, + ); + } + return expression..body = new BlockFunctionBody( + null, + null, + new Block( + null, + _method._statements.map/**/((s) => s.toAst(scope)).toList(), + null, + ), + ); + } + + @override + StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); +} + abstract class _LiteralExpression + extends _ExpressionBase implements ExpressionBuilder, CodeBuilder { const _LiteralExpression(); @@ -311,6 +385,9 @@ abstract class _LiteralExpression [Scope scope = const Scope.identity()]) => _asFunctionExpression(this, scope); + @override + StatementBuilder asAssert() => new _AssertionStatementBuilder(this); + @override StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); } diff --git a/lib/src/builders/method_builder.dart b/lib/src/builders/method_builder.dart index 252c836..6855677 100644 --- a/lib/src/builders/method_builder.dart +++ b/lib/src/builders/method_builder.dart @@ -92,6 +92,9 @@ class MethodBuilder implements CodeBuilder { Declaration toAst([Scope scope = const Scope.identity()]) => toFunctionAst(scope); + /// Returns as a closure expression. + ExpressionBuilder toClosure() => new _ClosureExpressionBuilder(this); + /// Returns a copy-safe [FunctionDeclaration] AST representing current state. FunctionDeclaration toFunctionAst([Scope scope = const Scope.identity()]) { var functionAst = _emptyFunction() diff --git a/lib/src/builders/statement/statement.dart b/lib/src/builders/statement/statement.dart new file mode 100644 index 0000000..ef0fe4a --- /dev/null +++ b/lib/src/builders/statement/statement.dart @@ -0,0 +1,4 @@ +import 'package:analyzer/analyzer.dart' show Statement; +import 'package:code_builder/code_builder.dart' show CodeBuilder; + +class StatementBuilder implements CodeBuilder {} diff --git a/lib/src/builders/statement_builder.dart b/lib/src/builders/statement_builder.dart index 683753d..f794a37 100644 --- a/lib/src/builders/statement_builder.dart +++ b/lib/src/builders/statement_builder.dart @@ -9,6 +9,55 @@ abstract class StatementBuilder implements CodeBuilder { StatementBuilder._sealed(); } +/// Builds an `if` [Statement] AST. +class IfStatementBuilder implements CodeBuilder { + final ExpressionBuilder _condition; + final List _statements = []; + + IfStatementBuilder._(this._condition); + + /// Lazily adds [statement] to the then-clause of this if statement. + void addStatement(StatementBuilder statement) { + _statements.add(statement); + } + + @override + Statement toAst([Scope scope = const Scope.identity()]) { + return new IfStatement( + $if, + $openParen, + _condition.toAst(scope), + $closeParen, + new Block( + $openCurly, + _statements.map/**/((s) => s.toAst(scope)), + $closeCurly, + ), + null, + null, + ); + } +} + +class _AssertionStatementBuilder implements StatementBuilder { + final ExpressionBuilder _expression; + + _AssertionStatementBuilder(this._expression); + + @override + Statement toAst([Scope scope = const Scope.identity()]) { + return new AssertStatement( + null, + null, + _expression.toAst(scope), + null, + null, + null, + null, + ); + } +} + class _ExpressionStatementBuilder implements StatementBuilder { final ExpressionBuilder _expression; @@ -22,3 +71,18 @@ class _ExpressionStatementBuilder implements StatementBuilder { ); } } + +class _ReturnStatementBuilder implements StatementBuilder { + final ExpressionBuilder _expression; + + _ReturnStatementBuilder(this._expression); + + @override + Statement toAst([Scope scope = const Scope.identity()]) { + return new ReturnStatement( + $return, + _expression.toAst(scope), + $semicolon, + ); + } +} diff --git a/lib/src/builders/type_builder.dart b/lib/src/builders/type_builder.dart index cff6f2f..9bff9e2 100644 --- a/lib/src/builders/type_builder.dart +++ b/lib/src/builders/type_builder.dart @@ -19,7 +19,7 @@ class TypeBuilder implements CodeBuilder { : _importFrom = importFrom; @override - TypeName toAst([Scope scope = const Scope.identity()]) { + TypeName build([Scope scope = Scope.identity]) { return new TypeName(scope.getIdentifier(_identifier, _importFrom), null); } } diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart index e1036e0..281b6b7 100644 --- a/lib/src/pretty_printer.dart +++ b/lib/src/pretty_printer.dart @@ -14,7 +14,12 @@ String prettyToSource(AstNode astNode) { var buffer = new PrintBuffer(); var visitor = new _PrettyToSourceVisitor(buffer); astNode.accept(visitor); - return dartfmt(buffer.toString()); + var source = buffer.toString(); + try { + return dartfmt(source); + } on FormatterException catch (_) { + return source; + } } // TODO(matanl): Remove copied-pasted methods when API becomes available. diff --git a/lib/src/scope.dart b/lib/src/scope.dart index f852b0f..89e9c53 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -18,6 +18,9 @@ part of code_builder; /// print(scope.getIdentifier('Baz', 'package:bar/bar.dart'); /// } abstract class Scope { + /// A scoping context that does nothing. + static const identity = const _IdentityScope(); + /// Create a default scope context. /// /// Actual implementation is _not_ guaranteed, only that all import prefixes @@ -27,9 +30,6 @@ abstract class Scope { /// Create a context that just de-duplicates imports (no scoping). factory Scope.dedupe() = _DeduplicatingScope; - /// Create a context that does nothing. - const factory Scope.identity() = _IdentityScope; - /// Given a [symbol] and its known [importUri], return an [Identifier]. Identifier getIdentifier(String symbol, String importUri); diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index 2ca6c83..60f1809 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -5,10 +5,12 @@ import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; -// Keywords /// The `abstract` token. final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); +/// The `assert` token. +final Token $assert = new KeywordToken(Keyword.ASSERT, 0); + /// The `extends` token. final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); @@ -76,12 +78,18 @@ final Token $semicolon = new Token(TokenType.SEMICOLON, 0); /// The '=' token. final Token $equals = new Token(TokenType.EQ, 0); +/// The `if` token. +final Token $if = new KeywordToken(Keyword.IF, 0); + /// The '??=' token. final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); /// The '.' token. final Token $period = new Token(TokenType.PERIOD, 0); +/// The `return` token. +final Token $return = new KeywordToken(Keyword.RETURN, 0); + /// Returns a string token for the given string [s]. StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); diff --git a/lib/testing/equals_source.dart b/lib/testing/equals_source.dart index e79036c..87b37d1 100644 --- a/lib/testing/equals_source.dart +++ b/lib/testing/equals_source.dart @@ -5,6 +5,7 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/code_builder.dart'; import 'package:code_builder/src/tokens.dart'; +import 'package:dart_style/dart_style.dart'; import 'package:matcher/matcher.dart'; /// Returns identifiers that are just the file name. @@ -19,21 +20,35 @@ const Scope simpleNameScope = const _SimpleNameScope(); /// On failure, uses the default string matcher to show a detailed diff between /// the expected and actual source code results. /// -/// **NOTE**: By default it both runs `dartfmt` _and_ prints the source with -/// additional formatting over the default `Ast.toSource` implementation (i.e. -/// adds new lines between methods in classes, and more). -/// -/// If you have code that is not consider a valid compilation unit (like an -/// expression, you should flip [format] to `false`). +/// **NOTE**: Runs `dartfmt` _and_ prints the source with additional formatting +/// over the default `Ast.toSource` implementation (i.e. adds new lines between +/// methods in classes, and more). Matcher equalsSource( String source, { - bool format: true, - Scope scope: const Scope.identity(), + Scope scope: Scope.identity, +}) { + try { + source = dartfmt(source); + } on FormatterException catch (_) {} + return new _EqualsSource( + scope, + source, + ); +} + +/// Returns a [Matcher] that checks a [CodeBuilder versus [source]. +/// +/// On failure, uses the default string matcher to show a detailed diff between +/// the expected and actual source code results. +/// +/// **NOTE**: Whitespace is ignored. +Matcher equalsUnformatted( + String source, { + Scope scope: Scope.identity, }) { return new _EqualsSource( scope, - format ? dartfmt(source) : source, - format, + source, ); } @@ -42,9 +57,8 @@ Identifier _stringId(String s) => new SimpleIdentifier(stringToken(s)); class _EqualsSource extends Matcher { final Scope _scope; final String _source; - final bool _isFormatted; - _EqualsSource(this._scope, this._source, this._isFormatted); + _EqualsSource(this._scope, this._source); @override Description describe(Description description) { @@ -60,8 +74,7 @@ class _EqualsSource extends Matcher { ) { if (item is CodeBuilder) { var origin = _formatAst(item); - print(origin); - return equals(_source).describeMismatch( + return equalsIgnoringWhitespace(_source).describeMismatch( origin, mismatchDescription.addDescriptionOf(origin), matchState, @@ -75,18 +88,14 @@ class _EqualsSource extends Matcher { @override bool matches(item, _) { if (item is CodeBuilder) { - return _formatAst(item) == _source; + return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); } return false; } String _formatAst(CodeBuilder builder) { - var astNode = builder.toAst(_scope); - if (_isFormatted) { - return prettyToSource(astNode); - } else { - return astNode.toSource(); - } + var astNode = builder.build(_scope); + return prettyToSource(astNode); } } diff --git a/pubspec.yaml b/pubspec.yaml index 483ee3a..21acd58 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 0.1.3 +version: 1.0.0-dev description: A fluent API for generating Dart code author: Dart Team homepage: https://github.com/dart-lang/code_builder diff --git a/test/builders/method_builder_test.dart b/test/builders/method_builder_test.dart index 3b0c70e..9a8d4ca 100644 --- a/test/builders/method_builder_test.dart +++ b/test/builders/method_builder_test.dart @@ -340,4 +340,21 @@ void main() { }); }); }); + + test('should be able to emit a closure', () { + var assertion = (new MethodBuilder() + ..addStatement(literalTrue.asReturnStatement())) + .toClosure() + .asAssert(); + expect( + new MethodBuilder.returnVoid(name: 'main')..addStatement(assertion), + equalsSource(r''' + void main() { + assert(() { + return true; + }); + } + '''), + ); + }); } diff --git a/test/expression_test.dart b/test/expression_test.dart new file mode 100644 index 0000000..3477faa --- /dev/null +++ b/test/expression_test.dart @@ -0,0 +1,72 @@ +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/expression/expression.dart'; +import 'package:code_builder/testing/equals_source.dart'; +import 'package:test/test.dart'; + +void main() { + test('Literals', () { + test('literal(null) should emit null', () { + expect(literal(null), equalsSource('null')); + }); + + test('literal(true) should emit true', () { + expect(literal(true), equalsSource('true')); + }); + + test('literal(false) should emit false', () { + expect(literal(false), equalsSource('false')); + }); + + test('literal() should emit a string', () { + expect(literal('Hello'), equalsSource("'Hello'")); + }); + + test('literal() should throw', () { + expect(() => literal(new Object()), throwsArgumentError); + }); + }); + + group('$ExpressionBuilder', () { + test('asAssert() should emit an assert statement', () { + expect(literal(true).asAssert(), equalsSource(r''' + assert(true); + ''')); + }); + + test('assign should emit a setter', () { + expect(literal(true).assign('fancy'), equalsSource(r''' + fancy = true + ''')); + }); + + test('asConst should emit assignment to a new const variable', () { + expect(literal(true).asConst('fancy'), equalsSource(r''' + const fancy = true; + ''')); + }); + + test('asConst should emit assignment to a new typed const variable', () { + expect(literal(true).asConst('fancy', typeBool), equalsSource(r''' + const bool fancy = true; + ''')); + }); + + test('asFinal should emit assignment to a new final variable', () { + expect(literal(true).asFinal('fancy'), equalsSource(r''' + final fancy = true; + ''')); + }); + + test('asFinal should emit assignment to a new typed final variable', () { + expect(literal(true).asFinal('fancy', typeBool), equalsSource(r''' + final bool fancy = true; + ''')); + }); + + test('asVar should emit assignment to a new var', () { + expect(literal(true).asVar('fancy'), equalsSource(r''' + var fancy = true; + ''')); + }); + }); +} From 4fc528f68fa40d02092a02542a61063b14ae5e05 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 11:35:18 -0700 Subject: [PATCH 02/19] Incremental work. --- analysis_options.yaml | 2 +- lib/code_builder.dart | 34 +- lib/dart/core.dart | 55 +-- lib/src/builders/annotation.dart | 14 + lib/src/builders/annotation_builder.dart | 88 ---- lib/src/builders/class.dart | 55 +++ lib/src/builders/class/constructor.dart | 141 ++++++ lib/src/builders/class_builder.dart | 121 ------ lib/src/builders/constructor_builder.dart | 76 ---- lib/src/builders/expression.dart | 143 +++++++ lib/src/builders/expression/assert.dart | 20 + lib/src/builders/expression/assign.dart | 76 ++++ lib/src/builders/expression/binary.dart | 21 + lib/src/builders/expression/expression.dart | 275 ------------ lib/src/builders/expression/if.dart | 91 ++++ lib/src/builders/expression/invocation.dart | 76 ++++ lib/src/builders/expression/literal.dart | 56 +++ lib/src/builders/expression/mixin.dart | 118 +++++ lib/src/builders/expression/negate.dart | 18 + lib/src/builders/expression/operators.dart | 24 -- lib/src/builders/expression/parentheses.dart | 19 + lib/src/builders/expression/return.dart | 19 + lib/src/builders/expression_builder.dart | 402 ------------------ lib/src/builders/field_builder.dart | 111 ----- lib/src/builders/file_builder.dart | 163 ------- lib/src/builders/method_builder.dart | 195 --------- lib/src/builders/parameter.dart | 189 ++++++++ lib/src/builders/parameter_builder.dart | 159 ------- lib/src/builders/reference.dart | 60 +++ lib/src/builders/shared.dart | 15 + .../builders/shared/annotations_mixin.dart | 42 ++ lib/src/builders/shared/parameters_mixin.dart | 95 +++++ lib/src/builders/shared/statements_mixin.dart | 45 ++ lib/src/builders/statement.dart | 12 + lib/src/builders/statement/statement.dart | 4 - lib/src/builders/statement_builder.dart | 88 ---- lib/src/builders/type.dart | 49 +++ lib/src/builders/type_builder.dart | 25 -- lib/src/scope.dart | 13 +- lib/src/tokens.dart | 28 ++ lib/testing.dart | 1 + lib/testing/equals_source.dart | 10 +- test/builders/class_builder_test.dart | 167 -------- test/builders/field_builder_test.dart | 49 --- test/builders/file_builder_test.dart | 46 -- test/builders/method_builder_test.dart | 360 ---------------- test/class_test.dart | 129 ++++++ test/expression_test.dart | 90 +++- test/integration_test.dart | 151 ------- test/parameter_test.dart | 40 ++ test/reference_test.dart | 64 +++ test/scope_test.dart | 6 +- test/type_test.dart | 31 ++ 53 files changed, 1824 insertions(+), 2557 deletions(-) create mode 100644 lib/src/builders/annotation.dart delete mode 100644 lib/src/builders/annotation_builder.dart create mode 100644 lib/src/builders/class.dart create mode 100644 lib/src/builders/class/constructor.dart delete mode 100644 lib/src/builders/class_builder.dart delete mode 100644 lib/src/builders/constructor_builder.dart create mode 100644 lib/src/builders/expression.dart create mode 100644 lib/src/builders/expression/assert.dart create mode 100644 lib/src/builders/expression/assign.dart create mode 100644 lib/src/builders/expression/binary.dart delete mode 100644 lib/src/builders/expression/expression.dart create mode 100644 lib/src/builders/expression/if.dart create mode 100644 lib/src/builders/expression/invocation.dart create mode 100644 lib/src/builders/expression/literal.dart create mode 100644 lib/src/builders/expression/mixin.dart create mode 100644 lib/src/builders/expression/negate.dart delete mode 100644 lib/src/builders/expression/operators.dart create mode 100644 lib/src/builders/expression/parentheses.dart create mode 100644 lib/src/builders/expression/return.dart delete mode 100644 lib/src/builders/expression_builder.dart delete mode 100644 lib/src/builders/field_builder.dart delete mode 100644 lib/src/builders/file_builder.dart delete mode 100644 lib/src/builders/method_builder.dart create mode 100644 lib/src/builders/parameter.dart delete mode 100644 lib/src/builders/parameter_builder.dart create mode 100644 lib/src/builders/reference.dart create mode 100644 lib/src/builders/shared.dart create mode 100644 lib/src/builders/shared/annotations_mixin.dart create mode 100644 lib/src/builders/shared/parameters_mixin.dart create mode 100644 lib/src/builders/shared/statements_mixin.dart create mode 100644 lib/src/builders/statement.dart delete mode 100644 lib/src/builders/statement/statement.dart delete mode 100644 lib/src/builders/statement_builder.dart create mode 100644 lib/src/builders/type.dart delete mode 100644 lib/src/builders/type_builder.dart create mode 100644 lib/testing.dart delete mode 100644 test/builders/class_builder_test.dart delete mode 100644 test/builders/field_builder_test.dart delete mode 100644 test/builders/file_builder_test.dart delete mode 100644 test/builders/method_builder_test.dart create mode 100644 test/class_test.dart delete mode 100644 test/integration_test.dart create mode 100644 test/parameter_test.dart create mode 100644 test/reference_test.dart create mode 100644 test/type_test.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 8ea35ea..b8c9a7b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -15,7 +15,7 @@ linter: - valid_regexps - always_declare_return_types - annotate_overrides - - avoid_as + # - avoid_as - avoid_init_to_null - avoid_return_types_on_setters - await_only_futures diff --git a/lib/code_builder.dart b/lib/code_builder.dart index aaef5f3..1311fa6 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -36,17 +36,21 @@ import 'package:meta/meta.dart'; import 'src/analyzer_patch.dart'; import 'src/tokens.dart'; -part 'src/builders/annotation_builder.dart'; -part 'src/builders/class_builder.dart'; -part 'src/builders/constructor_builder.dart'; -part 'src/builders/expression_builder.dart'; -part 'src/builders/expression/operators.dart'; -part 'src/builders/field_builder.dart'; -part 'src/builders/file_builder.dart'; -part 'src/builders/method_builder.dart'; -part 'src/builders/parameter_builder.dart'; -part 'src/builders/statement_builder.dart'; -part 'src/builders/type_builder.dart'; +export 'src/builders/annotation.dart' show annotation, AnnotationBuilder; +export 'src/builders/class.dart' + show + clazz, + constructor, + constructorNamed, + initializer, + ClassBuilder, + ConstructorBuilder; +export 'src/builders/expression.dart' show ExpressionBuilder, literal; +export 'src/builders/parameter.dart' show parameter, ParameterBuilder; +export 'src/builders/reference.dart' show reference; +export 'src/builders/statement.dart' show StatementBuilder; +export 'src/builders/type.dart' show type, TypeBuilder; + part 'src/pretty_printer.dart'; part 'src/scope.dart'; @@ -66,7 +70,13 @@ String dartfmt(String source) { SimpleIdentifier _stringIdentifier(String s) => new SimpleIdentifier(stringToken(s)); -Literal _stringLiteral(String s) => new SimpleStringLiteral(stringToken(s), s); +/// A builder that emits a _specific_ Dart language [AstNode]. +abstract class AstBuilder { + /// Returns a copy-safe [AstNode] representing the current builder state. + /// + /// Uses [scope] to output an AST re-written to use appropriate prefixes. + AstNode buildAst([Scope scope = Scope.identity]); +} /// Base class for building and emitting a Dart language [AstNode]. abstract class CodeBuilder { diff --git a/lib/dart/core.dart b/lib/dart/core.dart index f8c4e21..2561a1c 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -9,118 +9,126 @@ /// to be automatically generated (at least partially) in the near future. library code_builder.dart.core; -import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/reference.dart'; +import 'package:code_builder/src/builders/type.dart'; /// An alias for `new AnnotationBuilder('override')`. -const AnnotationBuilder atOverride = const AnnotationBuilder.reference( +final ReferenceBuilder atOverride = reference( 'override', 'dart:core', ); +/// A reference to the `print` method. +final ReferenceBuilder printMethod = reference( + 'print', + 'dart:core', +); + /// An alias for `new TypeBuilder('bool')`. -const TypeBuilder typeBool = const TypeBuilder( +final TypeBuilder typeBool = new TypeBuilder( 'bool', importFrom: 'dart:core', ); /// An alias for `new TypeBuilder('DateTime')`. -const TypeBuilder typeDateTime = const TypeBuilder( +final TypeBuilder typeDateTime = new TypeBuilder( 'DateTime', importFrom: 'dart:core', ); /// An alias for `new TypeBuilder('double')`. -const TypeBuilder typeDouble = const TypeBuilder( +final TypeBuilder typeDouble = new TypeBuilder( 'double', importFrom: 'dart:core', ); /// An alias for `new TypeBuilder('Duration')`. -const TypeBuilder typeDuration = const TypeBuilder( +final TypeBuilder typeDuration = new TypeBuilder( 'Duration', importFrom: 'dart:core', ); /// An alias for `new TypeBuilder('Expando')`. -const TypeBuilder typeExpando = const TypeBuilder( +final TypeBuilder typeExpando = new TypeBuilder( 'Expando', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Function')`. -const TypeBuilder typeFunction = const TypeBuilder( +final TypeBuilder typeFunction = new TypeBuilder( 'Function', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('int')`. -const TypeBuilder typeInt = const TypeBuilder( +final TypeBuilder typeInt = new TypeBuilder( 'int', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Iterable')`. -const TypeBuilder typeIterable = const TypeBuilder( +final TypeBuilder typeIterable = new TypeBuilder( 'Iterable', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('List')`. -const TypeBuilder typeList = const TypeBuilder( +final TypeBuilder typeList = new TypeBuilder( 'List', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Map')`. -const TypeBuilder typeMap = const TypeBuilder( +final TypeBuilder typeMap = new TypeBuilder( 'Map', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Null')`. -const TypeBuilder typeNull = const TypeBuilder( +final TypeBuilder typeNull = new TypeBuilder( 'Null', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('num')`. -const TypeBuilder typeNum = const TypeBuilder( +final TypeBuilder typeNum = new TypeBuilder( 'num', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Object')`. -const TypeBuilder typeObject = const TypeBuilder( +final TypeBuilder typeObject = new TypeBuilder( 'Object', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Set')`. -const TypeBuilder typeSet = const TypeBuilder( +final TypeBuilder typeSet = new TypeBuilder( 'Set', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('String')`. -const TypeBuilder typeString = const TypeBuilder( +final TypeBuilder typeString = new TypeBuilder( 'String', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Symbol')`. -const TypeBuilder typeSymbol = const TypeBuilder( +final TypeBuilder typeSymbol = new TypeBuilder( 'Symbol', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Type')`. -const TypeBuilder typeType = const TypeBuilder( +final TypeBuilder typeType = new TypeBuilder( 'Type', importFrom: 'dart:core', ); /// An alias for `const TypeBuilder('Uri')`. -const TypeBuilder typeUri = const TypeBuilder( +final TypeBuilder typeUri = new TypeBuilder( 'Uri', importFrom: 'dart:core', ); @@ -128,10 +136,7 @@ const TypeBuilder typeUri = const TypeBuilder( /// Creates either a `@deprecated` or `@Deprecated('Message')` annotation. AnnotationBuilder atDeprecated([String message]) { if (message == null) { - return new AnnotationBuilder.reference('deprecated', 'dart:core'); + return reference('deprecated', 'dart:core'); } - return new AnnotationBuilder.invoke( - 'Deprecated', - positional: [new LiteralString(message)], - ); + throw new UnimplementedError(); } diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart new file mode 100644 index 0000000..013f086 --- /dev/null +++ b/lib/src/builders/annotation.dart @@ -0,0 +1,14 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/shared.dart'; + +/// Returns a new [AnnotationBuilder] referencing [identifier]. +AnnotationBuilder annotation(String identifier, [String importFrom]) { + return reference(identifier, importFrom); +} + +/// Builds an [Annotation] AST. +abstract class AnnotationBuilder implements AstBuilder, ValidParameterMember { + /// Returns as an [Annotation] AST. + Annotation buildAnnotation([Scope scope = Scope.identity]); +} diff --git a/lib/src/builders/annotation_builder.dart b/lib/src/builders/annotation_builder.dart deleted file mode 100644 index 1ce7b70..0000000 --- a/lib/src/builders/annotation_builder.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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; - -/// Builds a metadata [Annotation] to be added to other AST elements. -/// -/// An annotation may either be a reference to an existing `const` identifier -/// _or_ to an invocation of a `const` class constructor. -/// -/// To create a `@doNotUse` reference, use [AnnotationBuilder.reference]: -/// const doNotUse = #do_not_use; -/// -/// @doNotUse -/// void destroyTheWorld() { ... } -/// -/// To create a `@DoNotUse('Blows up')` use [AnnotationBuilder.invoke]. -abstract class AnnotationBuilder implements CodeBuilder { - /// Create a new annotated `const` [constructor] invocation. - /// - /// May optionally specify an [importFrom] to auto-prefix the annotation _if_ - /// attached to a library where the import is not specified or is prefixed. - factory AnnotationBuilder.invoke( - String constructor, { - String importFrom, - Iterable> positional: const [], - Map> named: const {}, - }) { - return new _ConstructorAnnotationBuilder( - constructor, - new ExpressionBuilder.invoke( - null, - positional: positional, - named: named, - ), - importFrom, - ); - } - - /// Create a new annotated `const` [identifier]. - /// - /// May optionally specify an [importFrom] to auto-prefix the annotation _if_ - /// attached to a library where the import is not specified or is prefixed. - const factory AnnotationBuilder.reference(String identifier, - [String importFrom]) = _ReferenceAnnotationBuilder; -} - -class _ConstructorAnnotationBuilder implements AnnotationBuilder { - final String _constructor; - final ExpressionBuilder _expression; - final String _importFrom; - - _ConstructorAnnotationBuilder(this._constructor, this._expression, - [this._importFrom]); - - @override - Annotation toAst([Scope scope = const Scope.identity()]) { - var expressionAst = _expression.toAst(scope); - if (expressionAst is MethodInvocation) { - return new Annotation( - null, - scope.getIdentifier(_constructor, _importFrom), - null, - null, - // TODO(matanl): InvocationExpression needs to be public API. - expressionAst.argumentList, - ); - } - throw new UnsupportedError('Expression must be InvocationExpression'); - } -} - -class _ReferenceAnnotationBuilder implements AnnotationBuilder { - final String _importFrom; - final String _reference; - - const _ReferenceAnnotationBuilder(this._reference, [this._importFrom]); - - @override - Annotation toAst([Scope scope = const Scope.identity()]) => new Annotation( - null, - scope.getIdentifier(_reference, _importFrom), - null, - null, - null, - ); -} diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart new file mode 100644 index 0000000..e42df8a --- /dev/null +++ b/lib/src/builders/class.dart @@ -0,0 +1,55 @@ +library code_builder.src.builders.clazz; + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +part 'class/constructor.dart'; + +/// Returns a new [ClassBuilder]. +/// +/// Shorthand for `new ClassBuilder` for more functional aficionados. +ClassBuilder clazz(String name, [Iterable members = const []]) { + final clazz = new ClassBuilder(name); + for (final member in members) { + if (member is AnnotationBuilder) { + clazz.addAnnotation(member); + } + } + return clazz; +} + +/// Builds a [ClassDeclaration] AST. +class ClassBuilder implements AstBuilder { + final List _annotations = []; + final String _name; + + /// Create a new `class`. + ClassBuilder(this._name); + + /// Adds [annotation]. + void addAnnotation(AnnotationBuilder annotation) { + _annotations.add(annotation); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) { + return new ClassDeclaration( + null, + _annotations + .map/**/((a) => a.buildAnnotation(scope)) + .toList(), + null, + $class, + new SimpleIdentifier(stringToken(_name)), + null, + null, + null, + null, + null, + null, + null, + ); + } +} diff --git a/lib/src/builders/class/constructor.dart b/lib/src/builders/class/constructor.dart new file mode 100644 index 0000000..f1f3f74 --- /dev/null +++ b/lib/src/builders/class/constructor.dart @@ -0,0 +1,141 @@ +part of code_builder.src.builders.clazz; + +void _addMembers( + ConstructorBuilder constructor, + Iterable members, +) { + for (final member in members) { + if (member is StatementBuilder) { + constructor.addStatement(member); + } else if (member is ConstructorInitializerBuilder) { + constructor.addInitializer(member); + } else if (member is ParameterBuilder) { + constructor.addPositionalParameter(member); + } + } +} + +/// Returns a new unnamed constructor with [members]. +ConstructorBuilder constructor([ + Iterable members = const [], +]) { + final constructor = new _ConstructorBuilderImpl._detached(null); + _addMembers(constructor, members); + return constructor; +} + +/// Returns a new named constructor with [members]. +ConstructorBuilder constructorNamed( + String name, [ + Iterable members = const [], +]) { + final constructor = new _ConstructorBuilderImpl._detached(name); + _addMembers(constructor, members); + return constructor; +} + +/// Return a new initializer of [field] to [expression]. +ConstructorInitializerBuilder initializer( + String field, ExpressionBuilder expression) { + return new ConstructorInitializerBuilder._(field, expression); +} + +/// A detached initializer statement for a [ConstructorBuilder]. +/// +/// See [initializer] and [ExpressionBuilder.asInitializer]. +class ConstructorInitializerBuilder implements ValidConstructorMember { + final String _field; + final ExpressionBuilder _initializeTo; + + ConstructorInitializerBuilder._(this._field, this._initializeTo); + + /// Returns a new [ConstructorInitializer] AST. + ConstructorInitializer buildConstructorInitializer( + [Scope scope = Scope.identity]) => + buildAst(scope); + + @override + AstNode buildAst([Scope scope = Scope.identity]) { + return new ConstructorFieldInitializer( + $this, + $period, + new SimpleIdentifier(stringToken(_field)), + $equals, + _initializeTo.buildExpression(scope), + ); + } +} + +/// Builds a [ConstructorDeclaration] AST. +abstract class ConstructorBuilder + implements AstBuilder, HasAnnotations, HasParameters, HasStatements { + /// Create a new [ConstructorBuilder] for a `class`, optionally named. + factory ConstructorBuilder(ClassBuilder clazz, [String name]) = + _ConstructorBuilderImpl; + + /// Adds an [initializer]. + void addInitializer(ConstructorInitializerBuilder initializer); + + /// Returns a new [ConstructorBuilder] attached to [clazz]. + ConstructorBuilder attachTo(ClassBuilder clazz); +} + +/// Builds a [ConstructorDeclaration] AST. +class _ConstructorBuilderImpl extends Object + with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin + implements ConstructorBuilder { + final ClassBuilder _clazz; + final List _initializers = + []; + final String _name; + + _ConstructorBuilderImpl(this._clazz, [this._name]); + + // Internal: Allows using the constructor() factory inside of clazz(). + _ConstructorBuilderImpl._detached([this._name]) : this._clazz = null; + + @override + ConstructorBuilder attachTo(ClassBuilder clazz) { + final constructor = new ConstructorBuilder(clazz, _name); + _initializers.forEach(constructor.addInitializer); + cloneStatementsTo(constructor); + cloneParametersTo(constructor); + return constructor; + } + + @override + void addInitializer(ConstructorInitializerBuilder initializer) { + _initializers.add(initializer); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) { + return new ConstructorDeclaration( + null, + toAnnotationAsts(scope), + null, + null, + null, + new SimpleIdentifier(stringToken(_clazz._name)), + _name != null ? $period : null, + _name != null ? new SimpleIdentifier(stringToken(_name)) : null, + toFormalParameterList(scope), + null, + _initializers + .map/**/( + (i) => i.buildConstructorInitializer(scope)) + .toList(), + null, + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + new Block( + $openCurly, + toStatementAsts(scope), + $closeCurly, + )), + ); + } +} diff --git a/lib/src/builders/class_builder.dart b/lib/src/builders/class_builder.dart deleted file mode 100644 index bd6a260..0000000 --- a/lib/src/builders/class_builder.dart +++ /dev/null @@ -1,121 +0,0 @@ -// 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; - -/// Builds a [ClassDeclaration] AST. -class ClassBuilder implements CodeBuilder { - final String _name; - final bool _isAbstract; - final TypeBuilder _extend; - final Iterable _implement; - final Iterable _mixin; - - final List _fields = []; - final List _methods = []; - final List _metadata = []; - final List _constructors = []; - - /// Create a new builder for a `class` named [name]. - /// - /// Optionally, define another class to [extend] or classes to either - /// [implement] or [mixin]. - factory ClassBuilder( - String name, { - TypeBuilder extend, - Iterable implement: const [], - Iterable mixin: const [], - }) => - new ClassBuilder._( - name, - false, - extend, - new List.unmodifiable(implement), - new List.unmodifiable(mixin), - ); - - /// Create a new builder for an `abstract class` named [name]. - factory ClassBuilder.asAbstract(String name, - {TypeBuilder extend, - Iterable implement: const [], - Iterable mixin: const []}) => - new ClassBuilder._( - name, - true, - extend, - new List.unmodifiable(implement), - new List.unmodifiable(mixin), - ); - - ClassBuilder._( - this._name, - this._isAbstract, - this._extend, - this._implement, - this._mixin, - ); - - /// Adds an annotation [builder] as metadata. - void addAnnotation(AnnotationBuilder builder) { - _metadata.add(builder); - } - - /// Adds a constructor [builder]. - void addConstructor(ConstructorBuilder builder) { - _constructors.add(builder); - } - - /// Adds a field [builder] as a member on the class. - void addField(FieldBuilder builder) { - _fields.add(builder); - } - - /// Adds a method [builder] as a member on the class. - void addMethod(MethodBuilder builder) { - _methods.add(builder); - } - - @override - ClassDeclaration toAst([Scope scope = const Scope.identity()]) { - var astNode = _emptyClassDeclaration()..name = _stringIdentifier(_name); - if (_isAbstract) { - astNode.abstractKeyword = $abstract; - } - if (_extend != null) { - astNode.extendsClause = new ExtendsClause($extends, _extend.toAst(scope)); - } - if (_implement.isNotEmpty) { - astNode.implementsClause = new ImplementsClause($implements, - _implement.map/**/((i) => i.toAst(scope)).toList()); - } - if (_mixin.isNotEmpty) { - astNode.withClause = new WithClause( - $with, _mixin.map/**/((i) => i.toAst(scope)).toList()); - } - astNode - ..metadata.addAll(_metadata.map/**/((a) => a.toAst(scope))); - astNode - ..members.addAll(_fields.map/**/((f) => f.toFieldAst(scope))) - ..members.addAll(_constructors.map/**/( - (c) => c.toAst(scope)..returnType = _stringIdentifier(_name))) - ..members - .addAll(_methods.map/**/((m) => m.toMethodAst(scope))); - return astNode; - } - - static ClassDeclaration _emptyClassDeclaration() => new ClassDeclaration( - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ); -} diff --git a/lib/src/builders/constructor_builder.dart b/lib/src/builders/constructor_builder.dart deleted file mode 100644 index 33b3e5e..0000000 --- a/lib/src/builders/constructor_builder.dart +++ /dev/null @@ -1,76 +0,0 @@ -// 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; - -/// Builds a [ConstructorDeclaration] AST. -/// -/// Similar to [MethodBuilder] but with constructor-only features. -class ConstructorBuilder implements CodeBuilder { - final bool _isConstant; - final String _name; - - final List _annotations = []; - final List _parameters = []; - final List _statements = []; - - /// Create a new builder for a constructor, optionally with a [name]. - factory ConstructorBuilder([String name]) { - return new ConstructorBuilder._(false, name); - } - - /// Create a new builder for a constructor, optionally with a [name]. - /// - /// The resulting constructor will be `const`. - factory ConstructorBuilder.isConst([String name]) { - return new ConstructorBuilder._(true, name); - } - - ConstructorBuilder._(this._isConstant, this._name); - - /// Lazily adds [annotation]. - /// - /// When the method is emitted as an AST, [AnnotationBuilder.toAst] is used. - void addAnnotation(AnnotationBuilder annotation) { - _annotations.add(annotation); - } - - /// Lazily adds [builder]. - /// - /// When the method is emitted as an AST, [ParameterBuilder.toAst] is used. - void addParameter(ParameterBuilder builder) { - _parameters.add(builder); - } - - /// Lazily adds [statement]. - /// - /// When the method is emitted as an AST, [StatementBuilder.toAst] is used. - void addStatement(StatementBuilder statement) { - _statements.add(statement); - } - - @override - ConstructorDeclaration toAst([Scope scope = const Scope.identity()]) { - var astNode = new ConstructorDeclaration( - null, - _annotations.map/**/((a) => a.toAst(scope)), - null, - null, - _isConstant ? $const : null, - null, - null, - _name != null ? _stringIdentifier(_name) : null, - MethodBuilder._emptyParameters() - ..parameters.addAll( - _parameters.map/**/((p) => p.toAst(scope))), - null, - null, - null, - _statements.isEmpty - ? new EmptyFunctionBody($semicolon) - : MethodBuilder._blockBody( - _statements.map/**/((s) => s.toAst(scope)))); - return astNode; - } -} diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart new file mode 100644 index 0000000..410066f --- /dev/null +++ b/lib/src/builders/expression.dart @@ -0,0 +1,143 @@ +library code_builder.src.builders.expression; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:code_builder/code_builder.dart' show Scope; +import 'package:code_builder/src/tokens.dart'; +import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/type.dart'; + +part 'expression/assert.dart'; +part 'expression/assign.dart'; +part 'expression/binary.dart'; +part 'expression/if.dart'; +part 'expression/invocation.dart'; +part 'expression/literal.dart'; +part 'expression/mixin.dart'; +part 'expression/negate.dart'; +part 'expression/parentheses.dart'; +part 'expression/return.dart'; + +/// Builds an [Expression] AST. +abstract class ExpressionBuilder + implements StatementBuilder, ValidParameterMember { + /// Returns _as_ a statement that `asserts` the expression. + StatementBuilder asAssert(); + + /// Returns _as_ an expression that assigns to an existing [variable]. + /// + /// __Example use__: + /// literalTrue.assign('foo') // Outputs "foo = true" + ExpressionBuilder assign(String variable); + + /// Returns _as_ a statement that assigns to a new `const` [variable]. + /// + /// __Example use__: + /// literalTrue.asConst('foo') // Outputs "const foo = true;" + StatementBuilder asConst(String variable, [TypeBuilder type]); + + /// Returns _as_ a statement that assigns to a new `final` [variable]. + /// + /// __Example use__: + /// literalTrue.asFinal('foo') // Outputs "final foo = true;" + StatementBuilder asFinal(String variable, [TypeBuilder type]); + + /// Returns _as_ a statement that assigns to a new [variable]. + /// + /// __Example use__: + /// literalTrue.asVar('foo') // Outputs "var foo = true;" + StatementBuilder asVar(String variable); + + /// Returns _as_ a statement that builds an `if` statement. + /// + /// __Example use__: + /// literalTrue.asIf() // Outputs "if (true) { ... }" + IfStatementBuilder asIf(); + + /// Returns _as_ a constructor initializer setting [field] + ConstructorInitializerBuilder asInitializer(String field); + + /// Returns _as_ a statement that `return`s this expression. + /// + /// __Example use__: + /// literalTrue.asReturn() // Outputs "return true;" + StatementBuilder asReturn(); + + /// Returns _as_ a statement. + /// + /// **NOTE**: An [ExpressionBuilder] _is_ a [StatementBuilder]; this cast + /// is only necessary if you explicitly want [buildAst] to return a + /// [Statement] object, not an [Expression]. + /// + /// __Example use__: + /// literalTrue.asStatement() // Outputs "true;" + StatementBuilder asStatement() => new _AsStatement(this); + + /// Returns _as_ an expression calling itself. + /// + /// __Example use__: + /// reference('foo').call() // Outputs "foo()" + ExpressionSelfInvocationBuilder call([Iterable arguments]); + + /// Returns _as_ an expression calling itself. + /// + /// Unlike [call], may specify either [named] and/or [positional] arguments. + ExpressionSelfInvocationBuilder callWith({ + Map named, + Iterable positional, + }); + + /// Returns _as_ an expression accessing and calling [method]. + /// + /// __Example use__: + /// literalTrue.invoke('toString') // Outputs true.toString() + ExpressionInvocationBuilder invoke( + String method, [ + Iterable arguments, + ]); + + /// Returns _as_ an expression accessing and calling [method]. + /// + /// Unlike [invoke], may specify either [named] and/or [positional] arguments. + ExpressionInvocationBuilder invokeWith( + String method, { + Map named, + Iterable positional, + }); + + /// Returns _as_ an expression using the equality operator on [other]. + /// + /// __Example use__: + /// literalTrue.equals(literalTrue) // Outputs "true == true" + ExpressionBuilder equals(ExpressionBuilder other); + + /// Returns _as_ an expression using the not-equals operator on [other]. + /// + /// __Example use__: + /// literalTrue.notEquals(literalTrue) // Outputs "true != true" + ExpressionBuilder notEquals(ExpressionBuilder other); + + /// Returns _as_ an expression negating this one. + /// + /// __Example use__: + /// literalTrue.not() // Outputs "!true" + ExpressionBuilder not(); + + /// Returns _as_ an expression summing this and [other]. + ExpressionBuilder operator +(ExpressionBuilder other); + + /// Returns _as_ an expression wrapped in parentheses. + /// + /// __Example use__: + /// literalTrue.parentheses() // Outputs "(true)" + ExpressionBuilder parentheses(); + + /// Returns as an [Expression] AST. + Expression buildExpression([Scope scope = Scope.identity]); + + /// Returns as a [Statement] AST. + @override + Statement buildStatement([Scope scope = Scope.identity]); +} diff --git a/lib/src/builders/expression/assert.dart b/lib/src/builders/expression/assert.dart new file mode 100644 index 0000000..e6a9ee2 --- /dev/null +++ b/lib/src/builders/expression/assert.dart @@ -0,0 +1,20 @@ +part of code_builder.src.builders.expression; + +class _AsAssert extends StatementBuilder { + final ExpressionBuilder _expression; + + _AsAssert(this._expression); + + @override + Statement buildStatement([Scope scope = Scope.identity]) { + return new AssertStatement( + $assert, + $openParen, + _expression.buildExpression(scope), + null, + null, + $closeParen, + $semicolon, + ); + } +} diff --git a/lib/src/builders/expression/assign.dart b/lib/src/builders/expression/assign.dart new file mode 100644 index 0000000..7dbb536 --- /dev/null +++ b/lib/src/builders/expression/assign.dart @@ -0,0 +1,76 @@ +part of code_builder.src.builders.expression; + +class _AsAssign extends ExpressionBuilderMixin { + final ExpressionBuilder _expression; + final String _name; + + _AsAssign(this._expression, this._name); + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + final name = new SimpleIdentifier(stringToken(_name)); + return new AssignmentExpression( + name, + $equals, + _expression.buildExpression(scope), + ); + } +} + +enum _AsAssignType { + asConst, + asFinal, + asVar, +} + +class _AsAssignStatement extends StatementBuilder { + final ExpressionBuilder _expression; + final String _name; + final _AsAssignType _keyword; + final TypeBuilder _type; + + _AsAssignStatement( + this._expression, + this._name, { + _AsAssignType keyword, + TypeBuilder type, + }) + : _keyword = keyword, + _type = type; + + @override + Statement buildStatement([Scope scope = Scope.identity]) { + final name = new SimpleIdentifier(stringToken(_name)); + Token token; + switch (_keyword) { + case _AsAssignType.asConst: + token = $const; + break; + case _AsAssignType.asFinal: + token = $final; + break; + case _AsAssignType.asVar: + token = $var; + break; + } + return new VariableDeclarationStatement( + new VariableDeclarationList( + null, + null, + token, + _type?.buildType(scope), + [ + new VariableDeclaration( + name, + $equals, + _expression.buildExpression(scope), + ) + ], + ), + $semicolon, + ); + } +} diff --git a/lib/src/builders/expression/binary.dart b/lib/src/builders/expression/binary.dart new file mode 100644 index 0000000..fe3ca82 --- /dev/null +++ b/lib/src/builders/expression/binary.dart @@ -0,0 +1,21 @@ +part of code_builder.src.builders.expression; + +class _BinaryExpression extends ExpressionBuilderMixin { + final ExpressionBuilder _a; + final ExpressionBuilder _b; + final Token _operator; + + _BinaryExpression(this._a, this._b, this._operator); + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + return new BinaryExpression( + _a.buildExpression(scope), + _operator, + _b.buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/expression/expression.dart b/lib/src/builders/expression/expression.dart deleted file mode 100644 index 9f2ecc2..0000000 --- a/lib/src/builders/expression/expression.dart +++ /dev/null @@ -1,275 +0,0 @@ -import 'package:analyzer/analyzer.dart' - show - AssertStatement, - AssignmentExpression, - BooleanLiteral, - Expression, - ExpressionStatement, - Literal, - NullLiteral, - SimpleIdentifier, - SimpleStringLiteral, - Statement, - VariableDeclaration, - VariableDeclarationList, - VariableDeclarationStatement; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:code_builder/code_builder.dart' - show CodeBuilder, Scope, TypeBuilder; -import 'package:code_builder/src/tokens.dart' - show - $assert, - $closeParen, - $const, - $equals, - $false, - $final, - $null, - $openParen, - $semicolon, - $true, - $var, - stringToken; -import 'package:code_builder/src/builders/statement/statement.dart' - show StatementBuilder; - -final Literal _null = new NullLiteral($null); -final Literal _true = new BooleanLiteral($true, true); -final Literal _false = new BooleanLiteral($false, false); - -final ExpressionBuilder _literalNull = new _LiteralExpression(_null); -final ExpressionBuilder _literalTrue = new _LiteralExpression(_true); -final ExpressionBuilder _literalFalse = new _LiteralExpression(_false); - -/// Return a literal [value]. -/// -/// Must either be `null` or [bool], [num], or [String]. -/// -/// __Example use__: -/// literal(null); -/// literal('Hello World'); -/// literal(5); -/// literal(false); -/// -/// **NOTE**: A string literal is automatically wrapped in single-quotes. -/// -/// TODO(matanl): Add support for literal [List] and [Map] of other literals. -ExpressionBuilder literal(value) { - if (value == null) { - return _literalNull; - } - if (value is bool) { - return value ? _literalTrue : _literalFalse; - } - if (value is String) { - return new _LiteralExpression(new SimpleStringLiteral( - stringToken("'$value'"), - value, - )); - } - if (value is num) {} - throw new ArgumentError('Invalid type: ${value.runtimeType}'); -} - -/// Builds an [Expression] AST. -abstract class ExpressionBuilder implements CodeBuilder { - /// Returns _as_ a statement that `asserts` the expression. - StatementBuilder asAssert(); - - /// Returns _as_ an expression that assigns to an existing [variable]. - /// - /// __Example use__: - /// literalTrue.assign('foo') // Outputs "foo = true" - ExpressionBuilder assign(String variable); - - /// Returns _as_ a statement that assigns to a new `const` [variable]. - /// - /// __Example use__: - /// literalTrue.asConst('foo') // Outputs "const foo = true;" - StatementBuilder asConst(String variable, [TypeBuilder type]); - - /// Returns _as_ a statement that assigns to a new `final` [variable]. - /// - /// __Example use__: - /// literalTrue.asFinal('foo') // Outputs "final foo = true;" - StatementBuilder asFinal(String variable, [TypeBuilder type]); - - /// Returns _as_ a statement that assigns to a new [variable]. - /// - /// __Example use__: - /// literalTrue.asVar('foo') // Outputs "var foo = true;" - StatementBuilder asVar(String variable); - - /// Returns _as_ a statement that builds an `if` statement. - /// - /// __Example use__: - /// literalTrue.asIf() // Outputs "if (true) { ... }" - /*If*/ StatementBuilder asIf(); - - /// Returns _as_ a statement that `return`s this expression. - /// - /// __Example use__: - /// literalTrue.asReturn() // Outputs "return true;" - StatementBuilder asReturn(); - - /// Returns _as_ a statement. - /// - /// An expression itself in Dart is a valid statement. - /// - /// __Example use__: - /// invoke('foo').asStatement() // Outputs "foo();" - StatementBuilder asStatement(); - - /// Returns _as_ an expression using the equality operator on [condition]. - /// - /// __Example use__: - /// literalTrue.equals(literalTrue) // Outputs "true == true" - ExpressionBuilder equals(ExpressionBuilder condition); - - /// Returns _as_ an expression using the not-equals operator on [condition]. - /// - /// __Example use__: - /// literalTrue.notEquals(literalTrue) // Outputs "true != true" - ExpressionBuilder notEquals(ExpressionBuilder condition); - - /// Returns _as_ an expression negating this one. - /// - /// __Example use__: - /// literalTrue.not() // Outputs "!true" - ExpressionBuilder not(); - - /// Returns _as_ an expression wrapped in parentheses. - /// - /// __Example use__: - /// literalTrue.parentheses() // Outputs "(true)" - ExpressionBuilder parentheses(); -} - -/// A partial implementation of [ExpressionBuilder] suitable as a mixin. -abstract class ExpressionBuilderMixin implements ExpressionBuilder { - @override - StatementBuilder asAssert() => new _AsAssert(this); - - @override - ExpressionBuilder assign(String variable) => new _AsAssign(this, variable); - - @override - StatementBuilder asConst(String variable, [TypeBuilder type]) => - new _AsAssignStatement(this, variable, - keyword: _AsAssignType.asConst, type: type); - - @override - StatementBuilder asFinal(String variable, [TypeBuilder type]) => - new _AsAssignStatement(this, variable, - keyword: _AsAssignType.asFinal, type: type); - - @override - StatementBuilder asVar(String variable, [TypeBuilder type]) => - new _AsAssignStatement(this, variable, - keyword: _AsAssignType.asVar, type: type); - - @override - StatementBuilder asStatement() => new _AsStatement(this); -} - -class _AsAssert implements StatementBuilder { - final ExpressionBuilder _expression; - - _AsAssert(this._expression); - - @override - Statement build([Scope scope = Scope.identity]) { - return new AssertStatement( - $assert, - $openParen, - _expression.build(scope), - null, - null, - $closeParen, - $semicolon, - ); - } -} - -class _AsAssign extends ExpressionBuilderMixin implements ExpressionBuilder { - final ExpressionBuilder _expression; - final String _name; - - _AsAssign(this._expression, this._name); - - @override - Expression build([Scope scope = Scope.identity]) { - final name = new SimpleIdentifier(stringToken(_name)); - return new AssignmentExpression( - name, - $equals, - _expression.build(scope), - ); - } -} - -class _AsAssignStatement implements StatementBuilder { - final ExpressionBuilder _expression; - final String _name; - final _AsAssignType _keyword; - final TypeBuilder _type; - - _AsAssignStatement( - this._expression, - this._name, { - _AsAssignType keyword, - TypeBuilder type, - }) - : _keyword = keyword, - _type = type; - - @override - Statement build([Scope scope = Scope.identity]) { - final name = new SimpleIdentifier(stringToken(_name)); - Token token; - switch (_keyword) { - case _AsAssignType.asConst: - token = $const; - break; - case _AsAssignType.asFinal: - token = $final; - break; - case _AsAssignType.asVar: - token = $var; - break; - } - return new VariableDeclarationStatement( - new VariableDeclarationList( - null, - null, - token, - _type?.build(scope), - [new VariableDeclaration(name, $equals, _expression.build(scope))], - ), - $semicolon, - ); - } -} - -class _AsStatement implements StatementBuilder { - final ExpressionBuilder _expression; - - _AsStatement(this._expression); - - @override - Statement build([Scope scope = Scope.identity]) { - return new ExpressionStatement(_expression.build(scope), $semicolon); - } -} - -class _LiteralExpression extends ExpressionBuilderMixin - implements CodeBuilder { - final Literal _literal; - - _LiteralExpression(this._literal); - - @override - Literal build([_]) => _literal; -} - -enum _AsAssignType { asConst, asFinal, asVar, } diff --git a/lib/src/builders/expression/if.dart b/lib/src/builders/expression/if.dart new file mode 100644 index 0000000..6d0d4c2 --- /dev/null +++ b/lib/src/builders/expression/if.dart @@ -0,0 +1,91 @@ +part of code_builder.src.builders.expression; + +/// Creates a new builder for creating an `if` statement. +class IfStatementBuilder implements StatementBuilder { + final ExpressionBuilder _condition; + final List _statements = []; + + IfStatementBuilder._(this._condition); + + /// Lazily adds a [statement] builder. + void addStatement(StatementBuilder statement) { + _statements.add(statement); + } + + /// Returns as a builder with `else` added. + ElseStatementBuilder andElse([ExpressionBuilder condition]) { + return new ElseStatementBuilder._(this, condition); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope = Scope.identity]) => + _buildElse([this], scope); + + static Statement _buildElse( + List stack, [ + Scope scope = Scope.identity, + ]) { + final pop = stack.removeLast(); + return new IfStatement( + $if, + $openParen, + pop._condition.buildExpression(scope), + $closeParen, + new Block( + $openCurly, + pop._statements + .map/**/((s) => s.buildStatement(scope)) + .toList(), + $closeCurly, + ), + stack.isNotEmpty ? $else : null, + stack.isEmpty + ? null + : stack.length > 1 || stack.last._condition != null + ? _buildElse(stack, scope) + : new Block( + $openCurly, + stack.last._statements + .map/**/((s) => s.buildStatement(scope)) + .toList(), + $closeCurly, + ), + ); + } + + /// Parent if-statement, if any. + IfStatementBuilder get parent => null; + + @override + String toString() => '$runtimeType {$_condition}'; +} + +/// Creates a new builder for creating an `else` statement. +class ElseStatementBuilder extends IfStatementBuilder { + final IfStatementBuilder _parent; + + ElseStatementBuilder._(this._parent, [ExpressionBuilder condition]) + : super._(condition); + + @override + Statement buildStatement([Scope scope = Scope.identity]) { + return IfStatementBuilder._buildElse( + _getChain().toList(), + scope, + ); + } + + Iterable _getChain() sync* { + var builder = this; + while (builder != null) { + yield builder; + builder = builder.parent; + } + } + + @override + IfStatementBuilder get parent => _parent; +} diff --git a/lib/src/builders/expression/invocation.dart b/lib/src/builders/expression/invocation.dart new file mode 100644 index 0000000..e382e9e --- /dev/null +++ b/lib/src/builders/expression/invocation.dart @@ -0,0 +1,76 @@ +part of code_builder.src.builders.expression; + +/// Builds an invocation expression AST. +class ExpressionSelfInvocationBuilder extends ExpressionBuilderMixin { + final List _positional; + final Map _named; + final ExpressionBuilder _target; + + ExpressionSelfInvocationBuilder._( + this._target, { + Iterable positional: const [], + Map named: const {}, + }) + : _positional = positional.toList(growable: false), + _named = new Map.from(named); + + ArgumentList _toArgumentList([Scope scope = Scope.identity]) { + final expressions = []; + for (final arg in _positional) { + expressions.add(arg.buildExpression(scope)); + } + for (final name in _named.keys) { + final arg = _named[name]; + expressions.add(new NamedExpression( + new Label( + new SimpleIdentifier( + stringToken(name), + ), + $colon, + ), + arg.buildExpression(scope), + )); + } + return new ArgumentList( + $openParen, + expressions, + $closeParen, + ); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + return new FunctionExpressionInvocation( + _target.buildExpression(scope), + null, + _toArgumentList(scope), + ); + } +} + +/// Builds an invocation expression AST. +class ExpressionInvocationBuilder extends ExpressionSelfInvocationBuilder { + final String _method; + + ExpressionInvocationBuilder._( + this._method, + ExpressionBuilder target, { + Map named, + Iterable positional, + }) + : super._(target, named: named, positional: positional); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + return new MethodInvocation( + _target.buildExpression(scope), + $period, + new SimpleIdentifier(stringToken(_method)), + null, + _toArgumentList(scope), + ); + } +} diff --git a/lib/src/builders/expression/literal.dart b/lib/src/builders/expression/literal.dart new file mode 100644 index 0000000..620418a --- /dev/null +++ b/lib/src/builders/expression/literal.dart @@ -0,0 +1,56 @@ +part of code_builder.src.builders.expression; + +class _LiteralExpression extends ExpressionBuilderMixin { + final Literal _literal; + + _LiteralExpression(this._literal); + + @override + AstNode buildAst([_]) => buildExpression(); + + @override + Expression buildExpression([_]) => _literal; +} + +final Literal _null = new NullLiteral($null); +final Literal _true = new BooleanLiteral($true, true); +final Literal _false = new BooleanLiteral($false, false); + +final ExpressionBuilder _literalNull = new _LiteralExpression(_null); +final ExpressionBuilder _literalTrue = new _LiteralExpression(_true); +final ExpressionBuilder _literalFalse = new _LiteralExpression(_false); + +/// Return a literal [value]. +/// +/// Must either be `null` or a primitive type. +/// +/// __Example use__: +/// literal(null); +/// literal('Hello World'); +/// literal(5); +/// literal(false); +/// +/// **NOTE**: A string literal is automatically wrapped in single-quotes. +/// +/// TODO(matanl): Add support for literal [List] and [Map] of other literals. +ExpressionBuilder literal(value) { + if (value == null) { + return _literalNull; + } + if (value is bool) { + return value ? _literalTrue : _literalFalse; + } + if (value is String) { + return new _LiteralExpression(new SimpleStringLiteral( + stringToken("'$value'"), + value, + )); + } + if (value is int) { + return new _LiteralExpression(new IntegerLiteral( + stringToken(value.toString()), + value, + )); + } + throw new ArgumentError('Invalid type: ${value.runtimeType}'); +} diff --git a/lib/src/builders/expression/mixin.dart b/lib/src/builders/expression/mixin.dart new file mode 100644 index 0000000..27ea05f --- /dev/null +++ b/lib/src/builders/expression/mixin.dart @@ -0,0 +1,118 @@ +part of code_builder.src.builders.expression; + +/// A partial implementation of [ExpressionBuilder] suitable as a mixin. +abstract class ExpressionBuilderMixin implements ExpressionBuilder { + @override + StatementBuilder asAssert() => new _AsAssert(this); + + @override + ExpressionBuilder assign(String variable) => new _AsAssign(this, variable); + + @override + StatementBuilder asConst(String variable, [TypeBuilder type]) => + new _AsAssignStatement(this, variable, + keyword: _AsAssignType.asConst, type: type); + + @override + StatementBuilder asFinal(String variable, [TypeBuilder type]) => + new _AsAssignStatement(this, variable, + keyword: _AsAssignType.asFinal, type: type); + + @override + StatementBuilder asVar(String variable, [TypeBuilder type]) => + new _AsAssignStatement(this, variable, + keyword: _AsAssignType.asVar, type: type); + + @override + StatementBuilder asStatement() => new _AsStatement(this); + + @override + ConstructorInitializerBuilder asInitializer(String field) => + initializer(field, this); + + @override + Statement buildStatement([Scope scope = Scope.identity]) { + return new ExpressionStatement( + buildExpression(scope), + $semicolon, + ); + } + + @override + IfStatementBuilder asIf() => new IfStatementBuilder._(this); + + @override + StatementBuilder asReturn() => new _AsReturn(this); + + @override + ExpressionSelfInvocationBuilder call([ + Iterable arguments, + ]) => + new ExpressionSelfInvocationBuilder._(this, positional: arguments); + + @override + ExpressionSelfInvocationBuilder callWith({ + Map named, + Iterable positional, + }) => + new ExpressionSelfInvocationBuilder._( + this, + positional: positional, + named: named, + ); + + @override + ExpressionInvocationBuilder invoke( + String method, [ + Iterable arguments, + ]) => + new ExpressionInvocationBuilder._(method, this, positional: arguments); + + @override + ExpressionInvocationBuilder invokeWith( + String name, { + Map named, + Iterable positional, + }) => + new ExpressionInvocationBuilder._( + name, + this, + positional: positional, + named: named, + ); + + @override + ExpressionBuilder equals(ExpressionBuilder other) { + return new _BinaryExpression(this, other, $equalsEquals); + } + + @override + ExpressionBuilder notEquals(ExpressionBuilder other) { + return new _BinaryExpression(this, other, $notEquals); + } + + @override + ExpressionBuilder not() => new _NegateExpression(this); + + @override + ExpressionBuilder parentheses() => new _ParenthesesExpression(this); + + @override + ExpressionBuilder operator +(ExpressionBuilder other) { + return new _BinaryExpression( + this, + other, + $plus, + ); + } +} + +class _AsStatement extends StatementBuilder { + final ExpressionBuilder _expression; + + _AsStatement(this._expression); + + @override + Statement buildStatement([Scope scope = Scope.identity]) => + _expression.buildStatement(scope); +} diff --git a/lib/src/builders/expression/negate.dart b/lib/src/builders/expression/negate.dart new file mode 100644 index 0000000..7267cd9 --- /dev/null +++ b/lib/src/builders/expression/negate.dart @@ -0,0 +1,18 @@ +part of code_builder.src.builders.expression; + +class _NegateExpression extends ExpressionBuilderMixin { + final ExpressionBuilder _expression; + + _NegateExpression(this._expression); + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + return new PrefixExpression( + $not, + _expression.buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/expression/operators.dart b/lib/src/builders/expression/operators.dart deleted file mode 100644 index 4b8a416..0000000 --- a/lib/src/builders/expression/operators.dart +++ /dev/null @@ -1,24 +0,0 @@ -// 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; - -class _BinaryExpression extends _ExpressionBase { - final ExpressionBuilder _left; - final ExpressionBuilder _right; - final Token _operator; - - _OperatorExpression.equals(this._a, this._b) : _operator = $equals; - - _OperatorExpression.notEquals(this._a, this._b) : _operator = $notEquals; - - @override - Expression toAst([Scope scope = const Scope.identity()]) { - return new BinaryExpression( - _left.toAst(scope), - _operator, - _right.toAst(scope), - ); - } -} diff --git a/lib/src/builders/expression/parentheses.dart b/lib/src/builders/expression/parentheses.dart new file mode 100644 index 0000000..f8bc321 --- /dev/null +++ b/lib/src/builders/expression/parentheses.dart @@ -0,0 +1,19 @@ +part of code_builder.src.builders.expression; + +class _ParenthesesExpression extends ExpressionBuilderMixin { + final ExpressionBuilder _expression; + + _ParenthesesExpression(this._expression); + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + return new ParenthesizedExpression( + $openParen, + _expression.buildExpression(scope), + $closeParen, + ); + } +} diff --git a/lib/src/builders/expression/return.dart b/lib/src/builders/expression/return.dart new file mode 100644 index 0000000..88765bb --- /dev/null +++ b/lib/src/builders/expression/return.dart @@ -0,0 +1,19 @@ +part of code_builder.src.builders.expression; + +class _AsReturn implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsReturn(this._expression); + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope = Scope.identity]) { + return new ReturnStatement( + $return, + _expression.buildExpression(scope), + $semicolon, + ); + } +} diff --git a/lib/src/builders/expression_builder.dart b/lib/src/builders/expression_builder.dart deleted file mode 100644 index 50b7bdb..0000000 --- a/lib/src/builders/expression_builder.dart +++ /dev/null @@ -1,402 +0,0 @@ -// 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; - -// Shared functionality between ExpressionBuilder and _LiteralExpression. -/// Represents an expression value of `false`. -const literalFalse = const LiteralBool(false); - -/// Represents an expression value of `null`. -const literalNull = const _LiteralNull(); - -/// Represents an expression value of `true`. -const literalTrue = const LiteralBool(true); - -// TODO(matanl): Make this part of the public API. See annotation_builder.dart. -// Returns wrapped as a [ExpressionFunctionBody] AST. -ExpressionFunctionBody _asFunctionBody( - CodeBuilder expression, - Scope scope, -) { - return new ExpressionFunctionBody( - null, - null, - expression.toAst(scope), - $semicolon, - ); -} - -// Returns wrapped as a [FunctionExpression] AST. -FunctionExpression _asFunctionExpression( - CodeBuilder expression, - Scope scope, -) { - return new FunctionExpression( - null, - new FormalParameterList( - $openParen, - const [], - null, - null, - $closeParen, - ), - _asFunctionBody(expression, scope), - ); -} - -ExpressionBuilder _invokeSelfImpl( - ExpressionBuilder self, - String name, { - Iterable> positional: const [], - Map> named: const {}, -}) { - return new _InvokeExpression.target( - name, - self, - positional, - named, - ); -} - -/// Builds an [Expression] AST. -/// -/// For simple literal expressions see: -/// - [LiteralBool] and [literalTrue] [literalFalse] -/// - [LiteralInt] -/// - [LiteralString] -/// - [literalNull] -abstract class ExpressionBuilder implements CodeBuilder { - /// Invoke [name] (which should be available in the local scope). - /// - /// May specify [positional] and [named] arguments. - factory ExpressionBuilder.invoke( - String name, { - String importFrom, - Iterable> positional: const [], - Map> named: const {}, - }) { - return new _InvokeExpression( - name, - new List>.unmodifiable(positional), - new Map>.unmodifiable(named), - importFrom, - ); - } - - /// Invoke the 'new' operator on [type]. - /// - /// May use a [name] of a constructor and [positional] and [named] arguments. - factory ExpressionBuilder.invokeNew( - TypeBuilder type, { - String name, - Iterable> positional: const [], - Map> named: const {}, - }) { - return new _InvokeExpression.newInstance( - type, - name, - new List>.unmodifiable(positional), - new Map>.unmodifiable(named), - ); - } - - /// Assign [left] to [right]. - /// - /// If [nullAware] is true, the assignment uses the `??=` operator. - factory ExpressionBuilder.assignment( - String left, CodeBuilder right, - {bool nullAware: false}) { - return new _AssignmentExpression(left, right, nullAware: nullAware); - } - - const ExpressionBuilder._(); - - /// Return a new [ExpressionBuilder] invoking the result of this expression. - ExpressionBuilder invokeSelf( - String name, { - Iterable> positional: const [], - Map> named: const {}, - }); // TODO(matanl): Rename to invoke when factory is removed. - - /// Returns a new statement of `assert(expression)`. - StatementBuilder asAssert(); - - /// Returns wrapped as a [ExpressionFunctionBody] AST. - ExpressionFunctionBody toFunctionBody([Scope scope = const Scope.identity()]); - - /// Returns wrapped as a [FunctionExpression] AST. - FunctionExpression toFunctionExpression( - [Scope scope = const Scope.identity()]); - - /// Returns as an `if () {' builder. - IfStatementBuilder asIfStatement(); - - /// Converts to a `return `. - StatementBuilder asReturnStatement(); - - /// Returns as an ` == ` expression. - ExpressionBuilder asEquals(ExpressionBuilder other); - - /// Returns as an ` != ` expression. - ExpressionBuilder asNotEquals(ExpressionBuilder other); - - /// Converts to a [StatementBuilder]. - /// - /// __Example use__: - /// literalNull.toStatement(); // ==> null; - StatementBuilder toStatement(); -} - -abstract class _ExpressionBase implements ExpressionBuilder { - const _ExpressionBase(); - - @override - StatementBuilder asAssert() => new _AssertionStatementBuilder(this); - - @override - ExpressionFunctionBody toFunctionBody( - [Scope scope = const Scope.identity()]) => - _asFunctionBody(this, scope); - - @override - FunctionExpression toFunctionExpression( - [Scope scope = const Scope.identity()]) => - _asFunctionExpression(this, scope); - - @override - IfStatementBuilder asIfStatement() => new IfStatementBuilder._(this); - - @override - StatementBuilder asReturnStatement() => new _ReturnStatementBuilder(this); -} - -/// Creates a new literal `bool` value. -class LiteralBool extends _LiteralExpression { - static final BooleanLiteral _true = new BooleanLiteral($true, true); - static final BooleanLiteral _false = new BooleanLiteral($false, false); - - final bool _value; - - /// Returns the passed value as a [BooleanLiteral]. - const LiteralBool(this._value); - - @override - BooleanLiteral toAst([_]) => _value ? _true : _false; -} - -/// Represents an expression value of a literal number. -class LiteralInt extends _LiteralExpression { - final int _value; - - /// Returns the passed value as a [IntegerLiteral]. - const LiteralInt(this._value); - - @override - IntegerLiteral toAst([_]) => new IntegerLiteral( - intToken(_value), - _value, - ); -} - -/// Represents an expression value of a literal `'string'`. -class LiteralString extends _LiteralExpression { - final String _value; - - /// Returns the passed value as a [StringLiteral]. - const LiteralString(this._value); - - @override - StringLiteral toAst([_]) => new SimpleStringLiteral( - stringToken("'$_value'"), - _value, - ); -} - -class _InvokeExpression extends _ExpressionBase { - final String _importFrom; - final ExpressionBuilder _target; - final String _name; - final List> _positionalArguments; - final Map> _namedArguments; - final TypeBuilder _type; - - const _InvokeExpression( - this._name, - this._positionalArguments, - this._namedArguments, - this._importFrom, - ) - : _target = null, - _type = null; - - const _InvokeExpression.newInstance( - this._type, - this._name, - this._positionalArguments, - this._namedArguments, - ) - : _target = null, - _importFrom = null; - - const _InvokeExpression.target( - this._name, this._target, this._positionalArguments, this._namedArguments) - : _importFrom = null, - _type = null; - - @override - ExpressionBuilder invokeSelf( - String name, { - Iterable> positional: const [], - Map> named: const {}, - }) => - _invokeSelfImpl(this, name, positional: positional, named: named); - - @override - Expression toAst([Scope scope = const Scope.identity()]) { - // TODO(matanl): Move to TypeBuilder.newInstance. - if (_type != null) { - return new InstanceCreationExpression( - $new, - new ConstructorName( - _type.toAst(scope), - _name != null ? $period : null, - _name != null ? _stringIdentifier(_name) : null, - ), - _getArgumentList(scope), - ); - } - return new MethodInvocation( - _target?.toAst(scope), - _target != null ? $period : null, - _stringIdentifier(_name), - null, - _getArgumentList(scope), - ); - } - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); - - ArgumentList _getArgumentList(Scope scope) { - return new ArgumentList( - $openCurly, - _positionalArguments.map/* p.toAst(scope)).toList() - ..addAll(_namedArguments.keys - .map/**/((name) => new NamedExpression( - new Label( - _stringIdentifier(name), - $colon, - ), - _namedArguments[name].toAst(scope), - ))), - $closeCurly, - ); - } -} - -class _AssignmentExpression extends _ExpressionBase { - final String left; - final CodeBuilder right; - final bool nullAware; - - _AssignmentExpression(this.left, this.right, {this.nullAware: false}); - - @override - ExpressionBuilder invokeSelf(String name, - {Iterable> positional: const [], - Map> named: const {}}) { - return _invokeSelfImpl(this, name, positional: positional, named: named); - } - - @override - Expression toAst([Scope scope = const Scope.identity()]) { - return new AssignmentExpression(_stringIdentifier(left), - nullAware ? $nullAwareEquals : $equals, right.toAst(scope)); - } - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); -} - -class _ClosureExpressionBuilder extends _ExpressionBase { - final MethodBuilder _method; - - _ClosureExpressionBuilder(this._method); - - @override - ExpressionBuilder invokeSelf(String name, - {Iterable> positional: const [], - Map> named: const {}}) { - return _invokeSelfImpl(this, name, positional: positional, named: named); - } - - @override - Expression toAst([Scope scope = const Scope.identity()]) { - final expression = new FunctionExpression( - null, - MethodBuilder._emptyParameters(), - null, - ); - if (_method._returnExpression != null) { - return expression..body = new ExpressionFunctionBody( - null, - null, - _method._returnExpression.toAst(scope), - $semicolon, - ); - } - return expression..body = new BlockFunctionBody( - null, - null, - new Block( - null, - _method._statements.map/**/((s) => s.toAst(scope)).toList(), - null, - ), - ); - } - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); -} - -abstract class _LiteralExpression - extends _ExpressionBase - implements ExpressionBuilder, CodeBuilder { - const _LiteralExpression(); - - @override - ExpressionBuilder invokeSelf( - String name, { - Iterable> positional: const [], - Map> named: const {}, - }) => - _invokeSelfImpl(this, name, positional: positional, named: named); - - @override - ExpressionFunctionBody toFunctionBody( - [Scope scope = const Scope.identity()]) => - _asFunctionBody(this, scope); - - @override - FunctionExpression toFunctionExpression( - [Scope scope = const Scope.identity()]) => - _asFunctionExpression(this, scope); - - @override - StatementBuilder asAssert() => new _AssertionStatementBuilder(this); - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); -} - -class _LiteralNull extends _LiteralExpression { - static final NullLiteral _null = new NullLiteral($null); - - const _LiteralNull(); - - @override - NullLiteral toAst([_]) => _null; -} diff --git a/lib/src/builders/field_builder.dart b/lib/src/builders/field_builder.dart deleted file mode 100644 index 82839b1..0000000 --- a/lib/src/builders/field_builder.dart +++ /dev/null @@ -1,111 +0,0 @@ -// 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; - -/// Builds either a [FieldDeclaration] or [VariableDeclaration] AST. -/// -/// While the API is the same, you may specifically ask for either a field -/// AST (for class members) via [toFieldAst] or a variable declaration (used -/// both at the top-level and within methods) via [toVariablesAst]. -class FieldBuilder implements CodeBuilder { - final bool _isConst; - final bool _isFinal; - final bool _isStatic; - final ExpressionBuilder _initialize; - final String _name; - final TypeBuilder _type; - - /// Create a new field builder. - /// - /// Optionally set a type and initializer. - FieldBuilder( - this._name, { - TypeBuilder type, - ExpressionBuilder initialize, - bool asStatic: false, - }) - : this._type = type, - this._initialize = initialize, - this._isFinal = false, - this._isConst = false, - this._isStatic = asStatic; - - /// Create a new field builder that emits a `const` field. - /// - /// Optionally set a type and initializer. - FieldBuilder.isConst( - this._name, { - TypeBuilder type, - ExpressionBuilder initialize, - bool asStatic: false, - }) - : this._type = type, - this._initialize = initialize, - this._isFinal = false, - this._isConst = true, - this._isStatic = false; - - /// Create a new field builder that emits a `final` field. - /// - /// Optionally set a type and initializer. - FieldBuilder.isFinal( - this._name, { - TypeBuilder type, - ExpressionBuilder initialize, - bool asStatic: false, - }) - : this._type = type, - this._initialize = initialize, - this._isFinal = true, - this._isConst = false, - this._isStatic = asStatic; - - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// **NOTE**: This method exists primarily for testing and compatibility with - /// the [CodeBuilder] ADT. When possible, invoke [toFieldAst] or - /// [toVariablesAst]. - @override - @visibleForTesting - Declaration toAst([Scope scope = const Scope.identity()]) => - toFieldAst(scope); - - /// Returns a copy-safe [FieldDeclaration] AST representing current state. - FieldDeclaration toFieldAst([Scope scope = const Scope.identity()]) => - new FieldDeclaration( - null, - null, - _isStatic ? $static : null, - toVariablesAst(scope), - null, - ); - - /// Returns a copy-safe [VariableDeclaration] AST representing current state. - VariableDeclarationList toVariablesAst( - [Scope scope = const Scope.identity()]) => - new VariableDeclarationList( - null, - null, - _getVariableKeyword(), - _type?.toAst(scope), - [ - new VariableDeclaration( - _stringIdentifier(_name), - _initialize != null ? $equals : null, - _initialize?.toAst(scope), - ) - ], - ); - - Token _getVariableKeyword() { - if (_isFinal) { - return $final; - } - if (_isConst) { - return $const; - } - return _type == null ? $var : null; - } -} diff --git a/lib/src/builders/file_builder.dart b/lib/src/builders/file_builder.dart deleted file mode 100644 index 1b386b7..0000000 --- a/lib/src/builders/file_builder.dart +++ /dev/null @@ -1,163 +0,0 @@ -// 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; - -CompilationUnit _emptyCompilationUnit() => new CompilationUnit( - null, - null, - null, - null, - null, - ); - -/// An `export` directive in a [LibraryBuilder]. -class ExportBuilder implements CodeBuilder { - final String _uri; - - /// Create a new `export` directive exporting [uri]. - factory ExportBuilder(String uri) = ExportBuilder._; - - const ExportBuilder._(this._uri); - - @override - ExportDirective toAst([_]) { - return _createExportDirective()..uri = _stringLiteral("'$_uri'"); - } - - static ExportDirective _createExportDirective() => new ExportDirective( - null, - null, - null, - null, - null, - null, - null, - ); -} - -/// Builds files of Dart source code. -/// -/// See [LibraryBuilder] and [PartBuilder] for concrete implementations. -abstract class FileBuilder implements CodeBuilder { - final List> _declarations = - >[]; - - FileBuilder._(); - - /// Adds [declaration]'s resulting AST to the source. - void addDeclaration(CodeBuilder declaration) { - _declarations.add(declaration); - } - - @override - @mustCallSuper - CompilationUnit toAst([Scope scope = const Scope.identity()]) => - _emptyCompilationUnit() - ..declarations - .addAll(_declarations.map/**/((d) => d.toAst(scope))); -} - -/// An `import` directive in a [FileBuilder]. -class ImportBuilder implements CodeBuilder { - final String _uri; - final String _prefix; - - /// Create a new `import` directive importing [uri]. - /// - /// Optionally prefix [prefix]. - const factory ImportBuilder(String uri, {String prefix}) = ImportBuilder._; - - const ImportBuilder._(this._uri, {String prefix}) : _prefix = prefix; - - @override - ImportDirective toAst([_]) => _createImportDirective() - ..uri = _stringLiteral("'$_uri'") - ..prefix = _prefix != null ? _stringIdentifier(_prefix) : null; - - static ImportDirective _createImportDirective() => new ImportDirective( - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ); -} - -/// Builds a standalone Dart library [CompilationUnit] AST. -class LibraryBuilder extends FileBuilder { - final String _name; - final Scope _scope; - - final List> _directives = >[]; - - /// Create a new standalone Dart library, optionally with a [name]. - factory LibraryBuilder([String name]) { - return new LibraryBuilder._(name, const Scope.identity()); - } - - /// Create a new standalone Dart library, optionally with a [name]. - /// - /// As references are added in the library that implements [CodeBuilder] - /// they are re-written to avoid collisions and the imports are automatically - /// included at the top with optional prefixes. - factory LibraryBuilder.scope({String name, Scope scope}) { - return new LibraryBuilder._(name, scope ?? new Scope()); - } - - LibraryBuilder._(this._name, this._scope) : super._(); - - /// Adds [directive]'s resulting AST to the source. - void addDirective(CodeBuilder directive) { - _directives.add(directive); - } - - @override - CompilationUnit toAst([_]) { - var originalAst = super.toAst(_scope); - if (_name != null) { - originalAst.directives.add( - new LibraryDirective( - null, - null, - $library, - new LibraryIdentifier([_stringIdentifier(_name)]), - null, - ), - ); - } - originalAst.directives..addAll(_directives.map((d) => d.toAst())); - originalAst.directives..addAll(_scope.getImports().map((i) => i.toAst())); - return originalAst; - } -} - -/// Builds a `part of` [CompilationUnit] AST for an existing Dart library. -class PartBuilder extends FileBuilder { - final String _name; - - /// Create a new `part of` source file. - factory PartBuilder(String name) = PartBuilder._; - - PartBuilder._(this._name) : super._(); - - @override - CompilationUnit toAst([Scope scope = const Scope.identity()]) { - var originalAst = super.toAst(); - originalAst.directives.add(new PartOfDirective( - null, - null, - $part, - $of, - new LibraryIdentifier([_stringIdentifier(_name)]), - null, - )); - return originalAst; - } -} diff --git a/lib/src/builders/method_builder.dart b/lib/src/builders/method_builder.dart deleted file mode 100644 index 6855677..0000000 --- a/lib/src/builders/method_builder.dart +++ /dev/null @@ -1,195 +0,0 @@ -// 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; - -/// Builds either a [MethodDeclaration] or a [FunctionDeclaration]. -/// -/// While the API is the same, you may specifically ask for either a method -/// AST (for class members) via [toMethodAst] or a function AST (used both at -/// the top level and within other methods) via [toFunctionAst]. -/// -/// To return nothing (`void`), use [MethodBuilder.returnVoid]. -class MethodBuilder implements CodeBuilder { - // Void is a "type" that is only valid as a return type on a method. - static const TypeBuilder _typeVoid = const TypeBuilder('void'); - - final List _annotations = []; - final String _name; - final List _parameters = []; - final List _statements = []; - - final bool _isAbstract; - final bool _isStatic; - - ExpressionBuilder _returnExpression; - TypeBuilder _returnType; - - /// Create a new method builder, - /// - /// Optionally set a [returns] type. - factory MethodBuilder({ - String name, - TypeBuilder returns, - bool abstract: false, - bool static: false, - }) { - return new MethodBuilder._(name, returns, static, abstract); - } - - /// Creates a `void`-returning MethodBuilder with [name]. - factory MethodBuilder.returnVoid({ - String name, - bool abstract: false, - bool static: false, - }) { - return new MethodBuilder._(name, _typeVoid, static, abstract); - } - - MethodBuilder._( - this._name, - this._returnType, - this._isStatic, - this._isAbstract, - ); - - /// Lazily adds [annotation]. - /// - /// When the method is emitted as an AST, [AnnotationBuilder.toAst] is used. - void addAnnotation(AnnotationBuilder annotation) { - _annotations.add(annotation); - } - - /// Lazily adds [parameter]. - /// - /// When the method is emitted as an AST, [ParameterBuilder.toAst] is used. - void addParameter(ParameterBuilder parameter) { - _parameters.add(parameter); - } - - /// Lazily adds [statement]. - /// - /// When the method is emitted as an AST, [StatementBuilder.toAst] is used. - void addStatement(StatementBuilder statement) { - _statements.add(statement); - } - - /// Lazily sets [expression] as the lambda result of this method invocation. - /// - /// When the method is emitted as an AST, [ExpressionBuilder.toAst] is used. - void setExpression(ExpressionBuilder expression) { - _returnExpression = expression; - } - - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// **NOTE**: This method exists primarily for testing and compatibility with - /// the [CodeBuilder] ADT. When possible, invoke [toFunctionAst] or - /// [toMethodAst]. - @override - @visibleForTesting - Declaration toAst([Scope scope = const Scope.identity()]) => - toFunctionAst(scope); - - /// Returns as a closure expression. - ExpressionBuilder toClosure() => new _ClosureExpressionBuilder(this); - - /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - FunctionDeclaration toFunctionAst([Scope scope = const Scope.identity()]) { - var functionAst = _emptyFunction() - ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) - ..name = _stringIdentifier(_name) - ..returnType = _returnType?.toAst(scope); - if (_returnExpression != null) { - functionAst.functionExpression = _returnExpression.toFunctionExpression(); - } else { - functionAst.functionExpression = new FunctionExpression( - null, - _emptyParameters(), - _blockBody(_statements.map/**/((s) => s.toAst(scope))), - ); - } - if (_parameters.isNotEmpty) { - functionAst.functionExpression.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toAst(scope))); - } - return functionAst; - } - - /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - MethodDeclaration toMethodAst([Scope scope = const Scope.identity()]) { - var methodAst = _emptyMethod() - ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) - ..name = _stringIdentifier(_name) - ..returnType = _returnType?.toAst(scope); - FunctionBody methodBody = _returnExpression?.toFunctionBody(scope); - if (_isStatic) { - methodAst.modifierKeyword = $static; - if (methodBody == null) { - methodBody = _blockBody(); - } - } - if (methodBody == null) { - methodBody = _isAbstract - ? new EmptyFunctionBody($semicolon) - : _blockBody(_statements.map/**/((s) => s.toAst(scope))); - } - if (_parameters.isNotEmpty) { - methodAst.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toAst(scope))); - } - methodAst.body = methodBody; - return methodAst; - } - - @override - String toString() => 'MethodBuilder ${toAst().toSource()}'; - - static FunctionBody _blockBody([Iterable statements]) => - new BlockFunctionBody( - null, - null, - new Block( - $openCurly, - statements?.toList(), - $closeCurly, - ), - ); - - static FunctionDeclaration _emptyFunction() => new FunctionDeclaration( - null, - null, - null, - null, - null, - null, - new FunctionExpression( - null, - _emptyParameters(), - _blockBody(), - ), - ); - // TODO: implement requiredImports - static MethodDeclaration _emptyMethod() => new MethodDeclaration( - null, - null, - null, - null, - null, - null, - null, - null, - null, - _emptyParameters(), - null, - ); - - static FormalParameterList _emptyParameters() => new FormalParameterList( - $openParen, - [], - null, - null, - $closeParen, - ); -} diff --git a/lib/src/builders/parameter.dart b/lib/src/builders/parameter.dart new file mode 100644 index 0000000..0eab028 --- /dev/null +++ b/lib/src/builders/parameter.dart @@ -0,0 +1,189 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Create a new parameter [name], optionally with [members]. +ParameterBuilder parameter( + String name, [ + Iterable members = const [], +]) { + var parameter = new ParameterBuilder( + name, + members.firstWhere( + (p) => p is TypeBuilder, + orElse: () => null, + ) as TypeBuilder, + ); + final expression = members.firstWhere( + (p) => p is ExpressionBuilder, + orElse: () => null, + ); + for (final member in members) { + if (member is AnnotationBuilder) { + parameter.addAnnotation(member); + } + } + if (expression != null) { + parameter = parameter.toDefault(expression); + } + return parameter; +} + +/// Builds a [FormalParameter] AST. +abstract class ParameterBuilder + implements AstBuilder, HasAnnotations, ValidConstructorMember { + /// Create a new parameter [name], optionally with [type]. + factory ParameterBuilder(String name, [TypeBuilder type]) = + _SimpleParameterBuilder; + + /// Returns a new [FormalParameter] AST as a named parameter. + FormalParameter buildNamed([Scope scope = Scope.identity]); + + /// Returns a new [FormalParameter] AST as a positional parameter. + FormalParameter buildPositional([Scope scope = Scope.identity]); + + /// Returns as a new [ParameterBuilder] with a default [expression] value. + ParameterBuilder toDefault([ExpressionBuilder expression]); + + /// Returns as a new [ParameterBuilder] that assigns to `this.{name}`. + ParameterBuilder toField(); +} + +abstract class _AbstractParameterBuilder extends Object + with HasAnnotationsMixin + implements ParameterBuilder { + final String _name; + final TypeBuilder _type; + + _AbstractParameterBuilder(this._name, this._type); + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildPositional(scope); + + @override + ParameterBuilder toDefault([ExpressionBuilder expression]) { + return new _DefaultParameterBuilder( + this, + expression, + ); + } + + @override + ParameterBuilder toField() { + final field = new _FieldParameterBuilder(_name, _type); + field.addAnnotations(getAnnotations()); + return field; + } +} + +class _DefaultParameterBuilder implements ParameterBuilder { + final _AbstractParameterBuilder _builder; + final ExpressionBuilder _expression; + + _DefaultParameterBuilder(this._builder, this._expression); + + @override + void addAnnotation(AnnotationBuilder annotation) { + _builder.addAnnotation(annotation); + } + + @override + void addAnnotations(Iterable annotations) { + _builder.addAnnotations(annotations); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildPositional(scope); + + @override + FormalParameter buildNamed([Scope scope = Scope.identity]) { + return new DefaultFormalParameter( + _builder.buildPositional(scope), + ParameterKind.NAMED, + _expression != null ? $colon : null, + _expression?.buildExpression(scope), + ); + } + + @override + FormalParameter buildPositional([Scope scope = Scope.identity]) { + return new DefaultFormalParameter( + _builder.buildPositional(scope), + ParameterKind.POSITIONAL, + _expression != null ? $equals : null, + _expression?.buildExpression(scope), + ); + } + + @override + ParameterBuilder toDefault([ExpressionBuilder expression]) { + return new _DefaultParameterBuilder(_builder, expression); + } + + @override + ParameterBuilder toField() { + return new _DefaultParameterBuilder(_builder.toField(), _expression); + } +} + +class _SimpleParameterBuilder extends _AbstractParameterBuilder { + _SimpleParameterBuilder(String name, [TypeBuilder type]) : super(name, type); + + @override + FormalParameter buildPositional([Scope scope = Scope.identity]) { + return new SimpleFormalParameter( + null, + toAnnotationAsts(scope), + null, + _type?.buildType(scope), + new SimpleIdentifier(stringToken(_name)), + ); + } + + @override + FormalParameter buildNamed([Scope scope = Scope.identity]) { + return new DefaultFormalParameter( + buildPositional(scope), + ParameterKind.NAMED, + null, + null, + ); + } +} + +class _FieldParameterBuilder extends _AbstractParameterBuilder { + _FieldParameterBuilder(String name, [TypeBuilder type]) : super(name, type); + + @override + FormalParameter buildPositional([Scope scope = Scope.identity]) { + return new FieldFormalParameter( + null, + toAnnotationAsts(scope), + null, + _type?.buildType(scope), + $this, + $period, + new SimpleIdentifier(stringToken(_name)), + null, + null, + ); + } + + @override + FormalParameter buildNamed([Scope scope = Scope.identity]) { + return new DefaultFormalParameter( + buildPositional(scope), + ParameterKind.NAMED, + null, + null, + ); + } + + @override + ParameterBuilder toField() { + final field = new _FieldParameterBuilder(_name, _type); + field.addAnnotations(getAnnotations()); + return field; + } +} diff --git a/lib/src/builders/parameter_builder.dart b/lib/src/builders/parameter_builder.dart deleted file mode 100644 index 9f38cd4..0000000 --- a/lib/src/builders/parameter_builder.dart +++ /dev/null @@ -1,159 +0,0 @@ -// 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; - -/// Build a [FormalParameter] AST. -/// -/// A parameter is part of a built [MethodBuilder], and can refer to a -/// class-member (field) variable (see the `field` property in the -/// constructors). -class ParameterBuilder implements CodeBuilder { - final String _name; - final bool _isField; - final bool _isOptional; - final bool _isNamed; - final TypeBuilder _type; - final ExpressionBuilder _defaultTo; - - final List _annotations = []; - - /// Create a new _required_ parameter of [name]. - factory ParameterBuilder( - String name, { - bool field: false, - TypeBuilder type, - }) { - return new ParameterBuilder._( - name, - field, - false, - false, - type, - null, - ); - } - - /// Create a new _optional_ _named_ parameter. - factory ParameterBuilder.named( - String name, { - bool field: false, - TypeBuilder type, - ExpressionBuilder defaultTo, - }) { - return new ParameterBuilder._optional( - name, - true, - field: field, - type: type, - defaultTo: defaultTo, - ); - } - - /// Create a new _optional_ (but positional) parameter. - /// - /// See [ParameterBuilder.named] for an optional _named_ parameter. - factory ParameterBuilder.optional( - String name, { - bool field: false, - TypeBuilder type, - ExpressionBuilder defaultTo, - }) { - return new ParameterBuilder._optional( - name, - false, - field: field, - type: type, - defaultTo: defaultTo, - ); - } - - ParameterBuilder._( - this._name, - this._isField, - this._isOptional, - this._isNamed, - this._type, - this._defaultTo, - ); - - // Actual implementation of optional and named. - factory ParameterBuilder._optional( - String name, - bool named, { - bool field: false, - TypeBuilder type, - ExpressionBuilder defaultTo, - }) { - return new ParameterBuilder._( - name, - field, - true, - named, - type, - defaultTo, - ); - } - - /// Adds a metadata annotation from [builder]. - void addAnnotation(AnnotationBuilder builder) { - _annotations.add(builder); - } - - @override - FormalParameter toAst([Scope scope = const Scope.identity()]) { - FormalParameter astNode; - if (_isField) { - astNode = _createFieldFormalParameter(scope); - } else { - astNode = _createSimpleFormalParameter(scope); - } - if (_isOptional) { - astNode = _createDefaultFormalParameter( - astNode, - _isNamed, - _defaultTo, - scope, - ); - } - astNode.metadata.addAll(_annotations.map/**/((a) => a.toAst())); - return astNode; - } - - FieldFormalParameter _createFieldFormalParameter(Scope scope) => - new FieldFormalParameter( - null, - null, - null, - _type?.toAst(scope), - $this, - null, - _stringIdentifier(_name), - null, - null, - ); - - SimpleFormalParameter _createSimpleFormalParameter(Scope scope) => - new SimpleFormalParameter( - null, - null, - null, - _type?.toAst(scope), - _stringIdentifier(_name), - ); - - static DefaultFormalParameter _createDefaultFormalParameter( - FormalParameter parameter, - bool named, - ExpressionBuilder defaultTo, - Scope scope, - ) { - return new DefaultFormalParameter( - parameter, - named ? ParameterKind.NAMED : ParameterKind.POSITIONAL, - defaultTo != null ? named ? $colon : $equals : null, - defaultTo?.toAst(), - ); - } -} diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart new file mode 100644 index 0000000..2d35fb7 --- /dev/null +++ b/lib/src/builders/reference.dart @@ -0,0 +1,60 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart' show Scope; +import 'package:code_builder/src/tokens.dart'; + +import 'annotation.dart'; +import 'expression.dart'; +import 'type.dart'; + +/// Create a reference to [identifier], optionally where to [importFrom]. +ReferenceBuilder reference(String identifier, [String importFrom]) { + return new ReferenceBuilder._(identifier, importFrom); +} + +/// A reference to a type, field, variable, etc. +/// +/// [ReferenceBuilder] does _not_ emit an AST itself, but rather is a +/// convenience builder for creating other forms of ASTs. +class ReferenceBuilder extends ExpressionBuilderMixin + implements AnnotationBuilder, TypeBuilder { + final String _identifier; + final String _importFrom; + + ReferenceBuilder._(this._identifier, [this._importFrom]); + + @override + void addGeneric(TypeBuilder type) { + throw new UnsupportedError('Use type() to be able to add generics'); + } + + @override + Annotation buildAnnotation([Scope scope = Scope.identity]) { + return new Annotation( + $at, + buildExpression(scope), + null, + null, + null, + ); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope = Scope.identity]) { + return scope.getIdentifier(_identifier, _importFrom); + } + + @override + TypeName buildType([Scope scope = Scope.identity]) { + return generic().buildType(scope); + } + + /// Returns converted to a [TypeBuilder]. + TypeBuilder generic([Iterable generics = const []]) { + final type = new TypeBuilder(_identifier, importFrom: _importFrom); + generics.forEach(type.addGeneric); + return type; + } +} diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart new file mode 100644 index 0000000..2340448 --- /dev/null +++ b/lib/src/builders/shared.dart @@ -0,0 +1,15 @@ +import 'package:code_builder/code_builder.dart'; + +export 'shared/annotations_mixin.dart'; +export 'shared/parameters_mixin.dart'; +export 'shared/statements_mixin.dart'; + +/// Marker interface for valid members of [ConstructorBuilder]. +abstract class ValidConstructorMember implements AstBuilder { + ValidConstructorMember._doNotExtend(); +} + +/// Marker interface for valid members of a [ParameterBuilder]. +abstract class ValidParameterMember implements AstBuilder { + ValidParameterMember._doNotExtend(); +} diff --git a/lib/src/builders/shared/annotations_mixin.dart b/lib/src/builders/shared/annotations_mixin.dart new file mode 100644 index 0000000..886d8d4 --- /dev/null +++ b/lib/src/builders/shared/annotations_mixin.dart @@ -0,0 +1,42 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; + +/// A builder that supports adding annotations as metadata. +abstract class HasAnnotations { + /// Lazily adds [annotation] as a builder. + /// + /// When the AST is built, [AnnotationBuilder.buildAnnotation] is invoked. + void addAnnotation(AnnotationBuilder annotation); + + /// Lazily adds [annotations] as builders. + /// + /// When the AST is built, [AnnotationBuilder.buildAnnotation] is invoked. + void addAnnotations(Iterable annotations); +} + +/// A mixin to add [addAnnotation] and [getAnnotations] to a builder. +abstract class HasAnnotationsMixin implements HasAnnotations { + final List _annotations = []; + + @override + void addAnnotation(AnnotationBuilder annotation) { + _annotations.add(annotation); + } + + @override + void addAnnotations(Iterable annotations) { + _annotations.addAll(annotations); + } + + /// Returns all annotations added by [addAnnotation]. + List getAnnotations() { + return new List.unmodifiable(_annotations); + } + + /// Returns a built list of [Annotation] ASTs. + List toAnnotationAsts([Scope scope = Scope.identity]) { + return _annotations + .map/**/((a) => a.buildAnnotation(scope)) + .toList(); + } +} diff --git a/lib/src/builders/shared/parameters_mixin.dart b/lib/src/builders/shared/parameters_mixin.dart new file mode 100644 index 0000000..22522c7 --- /dev/null +++ b/lib/src/builders/shared/parameters_mixin.dart @@ -0,0 +1,95 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// A builder that supports adding annotations as metadata. +abstract class HasParameters { + /// Lazily adds [parameter] as a builder for a named parameter. + /// + /// When the AST is built, [ParameterBuilder] is also built. + void addNamedParameter(ParameterBuilder parameter); + + /// Lazily adds [parameters] as builders for named parameters. + /// + /// When the AST is built, [ParameterBuilder] is also built. + void addNamedParameters(Iterable parameters); + + /// Lazily adds [parameter] as a builder for a positional parameter. + /// + /// When the AST is built, [ParameterBuilder] is also built. + void addPositionalParameter(ParameterBuilder parameter); + + /// Lazily adds [parameters] as builders for positional parameters. + /// + /// When the AST is built, [ParameterBuilder] is also built. + void addPositionalParameters(Iterable parameters); +} + +/// Associates a [parameter] with the [kind] that should be emitted. +/// +/// Convenience class for users of [HasParametersMixin]. +class ParameterWithKind { + /// Either a named or positional parameter. + final ParameterKind kind; + + /// Parameter builder. + final ParameterBuilder parameter; + + ParameterWithKind._positional(this.parameter) + : kind = ParameterKind.POSITIONAL; + + ParameterWithKind._named(this.parameter) : kind = ParameterKind.NAMED; +} + +/// A mixin to add [addAnnotation] and [getAnnotations] to a builder. +abstract class HasParametersMixin implements HasParameters { + final List _parameters = []; + + @override + void addNamedParameter(ParameterBuilder parameter) { + _parameters.add(new ParameterWithKind._named(parameter)); + } + + @override + void addNamedParameters(Iterable parameters) { + parameters.forEach(addNamedParameter); + } + + @override + void addPositionalParameter(ParameterBuilder parameter) { + _parameters.add(new ParameterWithKind._positional(parameter)); + } + + @override + void addPositionalParameters(Iterable parameters) { + parameters.forEach(addPositionalParameter); + } + + /// Clones all added parameters to [mixin]. + void cloneParametersTo(HasParameters mixin) { + for (final parameter in _parameters) { + if (parameter.kind == ParameterKind.POSITIONAL) { + mixin.addPositionalParameter(parameter.parameter); + } else { + mixin.addNamedParameter(parameter.parameter); + } + } + } + + /// Returns a built list of [Annotation] ASTs. + FormalParameterList toFormalParameterList([Scope scope = Scope.identity]) { + return new FormalParameterList( + $openParen, + _parameters.map/**/((p) { + if (p.kind == ParameterKind.POSITIONAL) { + return p.parameter.buildPositional(scope); + } else { + return p.parameter.buildNamed(scope); + } + }).toList(), + null, + null, + $closeParen, + ); + } +} diff --git a/lib/src/builders/shared/statements_mixin.dart b/lib/src/builders/shared/statements_mixin.dart new file mode 100644 index 0000000..77d33a3 --- /dev/null +++ b/lib/src/builders/shared/statements_mixin.dart @@ -0,0 +1,45 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; + +/// A builder that supports adding statements. +abstract class HasStatements { + /// Lazily adds [statement] as a builder. + /// + /// When the AST is built, [StatementBuilder.buildStatement] is invoked. + void addStatement(StatementBuilder statement); + + /// Lazily adds [statements] as builders. + /// + /// When the AST is built, [StatementBuilder.buildStatement] is invoked. + void addStatements(Iterable statements); +} + +/// A mixin to add [addStatement] and [addStatements] to a builder. +abstract class HasStatementsMixin implements HasStatements { + final List _statements = []; + + @override + void addStatement(StatementBuilder statement) { + _statements.add(statement); + } + + @override + void addStatements(Iterable statements) { + _statements.addAll(statements); + } + + /// Clones all added statements to [mixin]. + void cloneStatementsTo(HasStatements mixin) { + mixin.addStatements(_statements); + } + + /// Whether any statements have been added. + bool get hasStatements => _statements.isNotEmpty; + + /// Returns a built list of [Annotation] ASTs. + List toStatementAsts([Scope scope = Scope.identity]) { + return _statements + .map/**/((a) => a.buildStatement(scope)) + .toList(); + } +} diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart new file mode 100644 index 0000000..77ad7c8 --- /dev/null +++ b/lib/src/builders/statement.dart @@ -0,0 +1,12 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart' show AstBuilder, Scope; +import 'package:code_builder/src/builders/shared.dart'; + +/// Builds an [Statement] AST. +abstract class StatementBuilder implements AstBuilder, ValidConstructorMember { + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildStatement(scope); + + /// Returns a Dart [Statement] AST reprsenting the current builder state. + Statement buildStatement([Scope scope = Scope.identity]); +} diff --git a/lib/src/builders/statement/statement.dart b/lib/src/builders/statement/statement.dart deleted file mode 100644 index ef0fe4a..0000000 --- a/lib/src/builders/statement/statement.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:analyzer/analyzer.dart' show Statement; -import 'package:code_builder/code_builder.dart' show CodeBuilder; - -class StatementBuilder implements CodeBuilder {} diff --git a/lib/src/builders/statement_builder.dart b/lib/src/builders/statement_builder.dart deleted file mode 100644 index f794a37..0000000 --- a/lib/src/builders/statement_builder.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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; - -/// Builds a [Statement] AST. -abstract class StatementBuilder implements CodeBuilder { - StatementBuilder._sealed(); -} - -/// Builds an `if` [Statement] AST. -class IfStatementBuilder implements CodeBuilder { - final ExpressionBuilder _condition; - final List _statements = []; - - IfStatementBuilder._(this._condition); - - /// Lazily adds [statement] to the then-clause of this if statement. - void addStatement(StatementBuilder statement) { - _statements.add(statement); - } - - @override - Statement toAst([Scope scope = const Scope.identity()]) { - return new IfStatement( - $if, - $openParen, - _condition.toAst(scope), - $closeParen, - new Block( - $openCurly, - _statements.map/**/((s) => s.toAst(scope)), - $closeCurly, - ), - null, - null, - ); - } -} - -class _AssertionStatementBuilder implements StatementBuilder { - final ExpressionBuilder _expression; - - _AssertionStatementBuilder(this._expression); - - @override - Statement toAst([Scope scope = const Scope.identity()]) { - return new AssertStatement( - null, - null, - _expression.toAst(scope), - null, - null, - null, - null, - ); - } -} - -class _ExpressionStatementBuilder implements StatementBuilder { - final ExpressionBuilder _expression; - - _ExpressionStatementBuilder(this._expression); - - @override - Statement toAst([Scope scope = const Scope.identity()]) { - return new ExpressionStatement( - _expression.toAst(scope), - $semicolon, - ); - } -} - -class _ReturnStatementBuilder implements StatementBuilder { - final ExpressionBuilder _expression; - - _ReturnStatementBuilder(this._expression); - - @override - Statement toAst([Scope scope = const Scope.identity()]) { - return new ReturnStatement( - $return, - _expression.toAst(scope), - $semicolon, - ); - } -} diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart new file mode 100644 index 0000000..fe89965 --- /dev/null +++ b/lib/src/builders/type.dart @@ -0,0 +1,49 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Returns a new [TypeBuilder]. +/// +/// Shorthand for `new TypeBuilder` for more functional aficionados. +TypeBuilder type(String identifier, [String importFrom]) { + return new TypeBuilder(identifier, importFrom: importFrom); +} + +/// Builds an [TypeName] AST. +class TypeBuilder implements AstBuilder, ValidParameterMember { + final List _generics = []; + final String _identifier; + final String _importFrom; + + /// Create a new [TypeBuilder] from an identifier. + /// + /// Optionally specify where the reference should be [importFrom]. + TypeBuilder( + this._identifier, { + String importFrom, + }) + : _importFrom = importFrom; + + /// Adds a generic [type] parameter. + void addGeneric(TypeBuilder type) { + _generics.add(type); + } + + @override + AstNode buildAst([Scope scope = Scope.identity]) => buildType(scope); + + /// Returns a new [TypeName] AST. + TypeName buildType([Scope scope = Scope.identity]) { + return new TypeName( + scope.getIdentifier(_identifier, _importFrom), + _generics.isEmpty + ? null + : new TypeArgumentList( + $lt, + _generics.map/**/((g) => g.buildType(scope)).toList(), + $gt, + ), + ); + } +} diff --git a/lib/src/builders/type_builder.dart b/lib/src/builders/type_builder.dart deleted file mode 100644 index 9bff9e2..0000000 --- a/lib/src/builders/type_builder.dart +++ /dev/null @@ -1,25 +0,0 @@ -// 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; - -/// Build a [TypeName] AST. -class TypeBuilder implements CodeBuilder { - final String _identifier; - final String _importFrom; - - /// Create a builder for emitting a [TypeName] AST. - /// - /// Optionally specify what must be imported for this type to resolve. - /// - /// __Example use__: - /// const TypeBuilder('String', importFrom: 'dart:core') - const TypeBuilder(this._identifier, {String importFrom}) - : _importFrom = importFrom; - - @override - TypeName build([Scope scope = Scope.identity]) { - return new TypeName(scope.getIdentifier(_identifier, _importFrom), null); - } -} diff --git a/lib/src/scope.dart b/lib/src/scope.dart index 89e9c53..b453ccb 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -34,7 +34,7 @@ abstract class Scope { Identifier getIdentifier(String symbol, String importUri); /// Returns a list of all imports needed to resolve identifiers. - Iterable getImports(); + Iterable< /*ImportBuilder*/ dynamic> getImports(); } class _DeduplicatingScope implements Scope { @@ -47,8 +47,9 @@ class _DeduplicatingScope implements Scope { } @override - Iterable getImports() { - return _imports.map/* new ImportBuilder(i)); + Iterable< /*ImportBuilder*/ dynamic> getImports() { + return _imports + .map/* new /*ImportBuilder*/ dynamic(i)); } } @@ -59,7 +60,7 @@ class _IdentityScope implements Scope { Identifier getIdentifier(String symbol, _) => _stringIdentifier(symbol); @override - Iterable getImports() => const []; + Iterable< /*ImportBuilder*/ dynamic> getImports() => const []; } class _IncrementingScope implements Scope { @@ -75,8 +76,8 @@ class _IncrementingScope implements Scope { } @override - Iterable getImports() { + Iterable< /*ImportBuilder*/ dynamic> getImports() { return _imports.keys.map/* new ImportBuilder(i, prefix: '_i${_imports[i]}')); + (i) => new /*ImportBuilder*/ dynamic(i, prefix: '_i${_imports[i]}')); } } diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index 60f1809..cacd355 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -11,6 +11,9 @@ final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); /// The `assert` token. final Token $assert = new KeywordToken(Keyword.ASSERT, 0); +/// The `class` token. +final Token $class = new KeywordToken(Keyword.CLASS, 0); + /// The `extends` token. final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); @@ -57,6 +60,10 @@ final Token $null = new KeywordToken(Keyword.NULL, 0); final Token $new = new KeywordToken(Keyword.NEW, 0); // Simple tokens + +/// The `@` token. +final Token $at = new Token(TokenType.AT, 0); + /// The '(' token. final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); @@ -69,24 +76,45 @@ final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); /// The '}' token. final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); +/// The `>` token. +final Token $gt = new Token(TokenType.GT, 0); + +/// The `<` token. +final Token $lt = new Token(TokenType.LT, 0); + /// The ':' token. final Token $colon = new Token(TokenType.COLON, 0); +/// The `else` token. +final Token $else = new KeywordToken(Keyword.ELSE, 0); + /// The ';' token. final Token $semicolon = new Token(TokenType.SEMICOLON, 0); /// The '=' token. final Token $equals = new Token(TokenType.EQ, 0); +/// The `==` token. +final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); + /// The `if` token. final Token $if = new KeywordToken(Keyword.IF, 0); /// The '??=' token. final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); +/// The `!` token. +final Token $not = new Token(TokenType.BANG, 0); + +/// The `!=` token. +final Token $notEquals = new Token(TokenType.BANG_EQ, 0); + /// The '.' token. final Token $period = new Token(TokenType.PERIOD, 0); +/// The '+' token. +final Token $plus = new Token(TokenType.PLUS, 0); + /// The `return` token. final Token $return = new KeywordToken(Keyword.RETURN, 0); diff --git a/lib/testing.dart b/lib/testing.dart new file mode 100644 index 0000000..ad4d623 --- /dev/null +++ b/lib/testing.dart @@ -0,0 +1 @@ +export 'testing/equals_source.dart'; diff --git a/lib/testing/equals_source.dart b/lib/testing/equals_source.dart index 87b37d1..c5cfa7c 100644 --- a/lib/testing/equals_source.dart +++ b/lib/testing/equals_source.dart @@ -72,7 +72,7 @@ class _EqualsSource extends Matcher { Map matchState, bool verbose, ) { - if (item is CodeBuilder) { + if (item is AstBuilder) { var origin = _formatAst(item); return equalsIgnoringWhitespace(_source).describeMismatch( origin, @@ -87,14 +87,14 @@ class _EqualsSource extends Matcher { @override bool matches(item, _) { - if (item is CodeBuilder) { + if (item is AstBuilder) { return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); } return false; } - String _formatAst(CodeBuilder builder) { - var astNode = builder.build(_scope); + String _formatAst(AstBuilder builder) { + var astNode = builder.buildAst(_scope); return prettyToSource(astNode); } } @@ -118,5 +118,5 @@ class _SimpleNameScope implements Scope { } @override - Iterable getImports() => const []; + Iterable< /*ImportBuilder*/ dynamic> getImports() => const []; } diff --git a/test/builders/class_builder_test.dart b/test/builders/class_builder_test.dart deleted file mode 100644 index e20cb54..0000000 --- a/test/builders/class_builder_test.dart +++ /dev/null @@ -1,167 +0,0 @@ -// 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. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit an empty class', () { - expect(new ClassBuilder('Animal'), equalsSource('class Animal {}')); - }); - - test('should emit an abstract class', () { - expect( - new ClassBuilder.asAbstract('Animal'), - equalsSource('abstract class Animal {}'), - ); - }); - - test('should emit a class extending another class', () { - expect( - new ClassBuilder('Animal', extend: new TypeBuilder('Organism')), - equalsSource('class Animal extends Organism {}'), - ); - }); - - test('should emit a class implementing another class', () { - expect( - new ClassBuilder('Animal', implement: [new TypeBuilder('Delicious')]), - equalsSource('class Animal implements Delicious {}'), - ); - }); - - test('should emit a class extending and mixing in another class', () { - expect( - new ClassBuilder( - 'Animal', - extend: new TypeBuilder('Organism'), - mixin: [new TypeBuilder('Breathing')], - ), - equalsSource('class Animal extends Organism with Breathing {}'), - ); - }); - - test('should emit a class with a method', () { - expect( - new ClassBuilder('Animal') - ..addMethod(new MethodBuilder( - name: 'toString', - returns: typeString, - )..setExpression(const LiteralString('Delicious'))), - equalsSource(r''' - class Animal { - String toString() => 'Delicious'; - } - '''), - ); - }); - - test('should emit an abstract class with an abstract method', () { - expect( - new ClassBuilder.asAbstract('Animal') - ..addMethod( - new MethodBuilder.returnVoid(name: 'eat', abstract: true), - ), - equalsSource(r''' - abstract class Animal { - void eat(); - } - '''), - ); - }); - - test('should emit a class with a static method', () { - expect( - new ClassBuilder('Animal') - ..addMethod( - new MethodBuilder( - name: 'create', - static: true, - returns: new TypeBuilder('Animal'), - )..setExpression(literalNull), - ), - equalsSource(r''' - class Animal { - static Animal create() => null; - } - '''), - ); - }); - - test('should emit a class with a metadata annotation', () { - expect( - new ClassBuilder('Animal')..addAnnotation(atDeprecated()), - equalsSource(r''' - @deprecated - class Animal {} - '''), - ); - }); - - test('should emit a class with an invoking metadata annotation', () { - expect( - new ClassBuilder('Animal') - ..addAnnotation(atDeprecated('We ate them all')), - equalsSource(r''' - @Deprecated('We ate them all') - class Animal {} - '''), - ); - }); - - group('constructors', () { - ClassBuilder clazz; - - setUp(() { - clazz = new ClassBuilder('Animal'); - }); - - test('default constructor', () { - clazz.addConstructor(new ConstructorBuilder()); - expect( - clazz, - equalsSource( - r''' - class Animal { - Animal(); - } - ''', - )); - }); - - test('default const constructor', () { - clazz.addConstructor(new ConstructorBuilder.isConst()); - expect( - clazz, - equalsSource( - r''' - class Animal { - const Animal(); - } - ''', - )); - }); - - test('initializing fields', () { - clazz.addConstructor(new ConstructorBuilder() - ..addParameter( - new ParameterBuilder('a', field: true), - ) - ..addParameter( - new ParameterBuilder.optional('b', field: true), - )); - expect( - clazz, - equalsSource( - r''' - class Animal { - Animal(this.a, [this.b]); - } - ''', - )); - }); - }); -} diff --git a/test/builders/field_builder_test.dart b/test/builders/field_builder_test.dart deleted file mode 100644 index 95954b4..0000000 --- a/test/builders/field_builder_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -// 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. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit an unitialized simple variable', () { - expect( - new FieldBuilder('name'), - equalsSource(r'var name;'), - ); - }); - - test('should emit a typed variable', () { - expect( - new FieldBuilder('name', type: typeString), - equalsSource(r'String name;'), - ); - }); - - test('should emit an initialized variable', () { - expect( - new FieldBuilder( - 'name', - type: typeString, - initialize: const LiteralString('Jill User'), - ), - equalsSource(r"String name = 'Jill User';"), - ); - }); - - test('should emit a final variable', () { - expect( - new FieldBuilder.isFinal('name'), - equalsSource(r'final name;'), - ); - }); - - test('should emit a const variable', () { - expect( - new FieldBuilder.isConst('name'), - equalsSource(r'const name;'), - ); - }); -} diff --git a/test/builders/file_builder_test.dart b/test/builders/file_builder_test.dart deleted file mode 100644 index 8329b97..0000000 --- a/test/builders/file_builder_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -// 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. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit a blank file', () { - expect(new LibraryBuilder(), equalsSource('')); - }); - - test('should emit a file with a library directive', () { - expect(new LibraryBuilder('code_builder'), - equalsSource('library code_builder;')); - }); - - test('should emit a file with a part of directive', () { - expect( - new PartBuilder('code_builder'), - equalsSource('part of code_builder;'), - ); - }); - - test('should emit an import directive', () { - expect( - new ImportBuilder('package:foo/foo.dart'), - equalsSource("import 'package:foo/foo.dart';"), - ); - }); - - test('should emit an import directive and a prefix', () { - expect( - new ImportBuilder('package:foo/foo.dart', prefix: 'foo'), - equalsSource("import 'package:foo/foo.dart' as foo;"), - ); - }); - - test('should emit an export directive', () { - expect( - new ExportBuilder('package:foo/foo.dart'), - equalsSource("export 'package:foo/foo.dart';"), - ); - }); -} diff --git a/test/builders/method_builder_test.dart b/test/builders/method_builder_test.dart deleted file mode 100644 index 9a8d4ca..0000000 --- a/test/builders/method_builder_test.dart +++ /dev/null @@ -1,360 +0,0 @@ -// 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. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit a simple "void" method', () { - expect( - new MethodBuilder.returnVoid(name: 'main'), - equalsSource('void main() {}'), - ); - }); - - test('should emit a method returning a String', () { - expect( - new MethodBuilder(name: 'toString', returns: typeString), - equalsSource('String toString() {}'), - ); - }); - - test('should emit a method returning a scoped Type', () { - expect( - new MethodBuilder( - name: 'toFoo', - returns: new TypeBuilder('Foo', importFrom: 'package:foo/foo.dart'), - ) - ..addParameter(new ParameterBuilder( - 'context', - type: - new TypeBuilder('Context', importFrom: 'package:bar/bar.dart'), - )), - equalsSource( - r''' - foo.Foo toFoo(bar.Context context) {} - ''', - scope: simpleNameScope, - )); - }); - - test('should emit a method returning a scoped Type and scoped Param', () { - expect( - new MethodBuilder( - name: 'toFoo', - returns: new TypeBuilder('Foo', importFrom: 'package:foo/foo.dart'), - ), - equalsSource( - r''' - foo.Foo toFoo() {} - ''', - scope: simpleNameScope, - )); - }); - - test('should emit a method annotated', () { - expect( - new MethodBuilder(name: 'oldMethod')..addAnnotation(atDeprecated()), - equalsSource( - r''' - @deprecated - oldMethod() {} - ''', - )); - }); - - group('with an expression', () { - test('should return true', () { - expect( - new MethodBuilder(name: 'isSupported', returns: typeBool) - ..setExpression(literalTrue), - equalsSource( - r''' - bool isSupported() => true; - ''', - ), - ); - }); - - test('should return false', () { - expect( - new MethodBuilder(name: 'isSupported', returns: typeBool) - ..setExpression(literalFalse), - equalsSource( - r''' - bool isSupported() => false; - ''', - ), - ); - }); - - test('should return null', () { - expect( - new MethodBuilder(name: 'isSupported')..setExpression(literalNull), - equalsSource( - r''' - isSupported() => null; - ''', - ), - ); - }); - - test('should return "Hello World"', () { - expect( - new MethodBuilder(name: 'sayHello', returns: typeString) - ..setExpression( - const LiteralString('Hello World'), - ), - equalsSource( - r''' - String sayHello() => 'Hello World'; - ''', - ), - ); - }); - - group('with statements', () { - test('should work with an expression', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement(const LiteralString('Hello World').toStatement()), - equalsSource(r''' - void main() { - 'Hello World'; - } - '''), - ); - }); - - test('should work with an assignment', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement(new ExpressionBuilder.assignment( - 'foo', - const LiteralString('Hello World'), - ) - .toStatement()), - equalsSource(r''' - void main() { - foo = 'Hello World'; - } - '''), - ); - }); - - test('should work with a null-aware assignment', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement(new ExpressionBuilder.assignment( - 'foo', - const LiteralString('Hello World'), - nullAware: true, - ) - .toStatement()), - equalsSource(r''' - void main() { - foo ??= 'Hello World'; - } - '''), - ); - }); - - test('should work invoking an expression', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement( - new ExpressionBuilder.invoke('print', positional: [ - const LiteralString('Hello World').invokeSelf( - 'substring', - positional: [const LiteralInt(1)], - ) - ]).toStatement(), - ), - equalsSource(r''' - void main() { - print('Hello World'.substring(1)); - } - '''), - ); - }); - }); - - group('with parameters:', () { - MethodBuilder method; - - setUp(() { - method = new MethodBuilder.returnVoid(name: 'main'); - }); - - test('single required parameter', () { - method.addParameter(new ParameterBuilder('name', type: typeString)); - expect( - method, - equalsSource( - r''' - void main(String name) {} - ''', - ), - ); - }); - - test('two required parameters', () { - method - ..addParameter(new ParameterBuilder('a', type: typeInt)) - ..addParameter(new ParameterBuilder('b', type: typeInt)); - expect( - method, - equalsSource( - r''' - void main(int a, int b) {} - ''', - ), - ); - }); - - test('one optional parameter', () { - method.addParameter(new ParameterBuilder.optional( - 'name', - type: typeString, - )); - expect( - method, - equalsSource( - r''' - void main([String name]) {} - ''', - ), - ); - }); - - test('one optional parameter with a default value', () { - method.addParameter(new ParameterBuilder.optional( - 'enabled', - type: typeBool, - defaultTo: literalTrue, - )); - expect( - method, - equalsSource( - r''' - void main([bool enabled = true]) {} - ''', - ), - ); - }); - - test('one optional named parameter with a default value', () { - method.addParameter(new ParameterBuilder.named( - 'enabled', - type: typeBool, - defaultTo: literalTrue, - )); - expect( - method, - equalsSource( - r''' - void main({bool enabled: true}) {} - ''', - ), - ); - }); - - test('one optional named parameter with an annotation', () { - method.addParameter(new ParameterBuilder.named( - 'enabled', - type: typeBool, - defaultTo: literalTrue, - )..addAnnotation(atDeprecated())); - expect( - method, - equalsSource( - r''' - void main({@deprecated bool enabled: true}) {} - ''', - ), - ); - }); - }); - - group('invoking method', () { - MethodBuilder method; - - setUp(() { - method = new MethodBuilder(name: 'forward', returns: typeInt); - }); - - test('should call another method with one argument', () { - method.setExpression(new ExpressionBuilder.invoke( - 'forwardImpl', - positional: [const LiteralInt(666)], - )); - expect( - method, - equalsSource(r''' - int forward() => forwardImpl(666); - '''), - ); - }); - - test('should call another method with two arguments', () { - method.setExpression(new ExpressionBuilder.invoke( - 'sum', - positional: [const LiteralInt(1), const LiteralInt(2)], - )); - expect( - method, - equalsSource(r''' - int forward() => sum(1, 2); - '''), - ); - }); - - test('should call another method with a named arguments', () { - method.setExpression(new ExpressionBuilder.invoke( - 'forwardImpl', - named: {'value': const LiteralInt(666)}, - )); - expect( - method, - equalsSource(r''' - int forward() => forwardImpl(value: 666); - '''), - ); - }); - - test('should call another method with many arguments', () { - method.setExpression(new ExpressionBuilder.invoke( - 'forwardImpl', - positional: const [const LiteralInt(3), const LiteralInt(4)], - named: {'a': const LiteralInt(1), 'b': const LiteralInt(2)}, - )); - expect( - method, - equalsSource(r''' - int forward() => forwardImpl(3, 4, a: 1, b: 2); - '''), - ); - }); - }); - }); - - test('should be able to emit a closure', () { - var assertion = (new MethodBuilder() - ..addStatement(literalTrue.asReturnStatement())) - .toClosure() - .asAssert(); - expect( - new MethodBuilder.returnVoid(name: 'main')..addStatement(assertion), - equalsSource(r''' - void main() { - assert(() { - return true; - }); - } - '''), - ); - }); -} diff --git a/test/class_test.dart b/test/class_test.dart new file mode 100644 index 0000000..e684f9f --- /dev/null +++ b/test/class_test.dart @@ -0,0 +1,129 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing/equals_source.dart'; +import 'package:test/test.dart'; + +void main() { + group('$ClassBuilder', () { + test('should emit a simple class', () { + expect(clazz('Animal'), equalsSource(r''' + class Animal {} + ''')); + }); + + test('should emit an annotated class', () { + expect( + clazz('Animal', [ + annotation('deprecated'), + ]), + equalsSource(r''' + @deprecated + class Animal {} + '''), + ); + }); + }); + + group('$ConstructorBuilder', () { + ClassBuilder animal; + + setUp(() => animal = clazz('Animal')); + + test('should emit a simple default constructor', () { + expect( + constructor().attachTo(animal), + equalsSource(r''' + Animal(); + '''), + ); + }); + + test('should emit a named constructor', () { + expect( + constructorNamed('empty').attachTo(animal), + equalsSource(r''' + Animal.empty(); + '''), + ); + }); + + test('should emit a constructor with a statement body', () { + expect( + constructor([ + printMethod.call([literal('Hello World')]), + ]).attachTo(animal), + equalsSource(r''' + Animal() { + print('Hello World'); + } + '''), + ); + }); + + test('should emit a constructor with constructor initializers', () { + expect( + constructor([ + initializer('age', literal(0)), + initializer('delicious', literal(true)), + ]).attachTo(animal), + equalsSource(r''' + Animal() + : this.age = 0, + this.delicious = true; + '''), + ); + }); + + test('should emit a constructor with a parameter', () { + expect( + constructor([ + parameter('foo'), + ]).attachTo(animal), + equalsSource(r''' + Animal(foo); + '''), + ); + }); + + test('should emit a constructor with an optional parameter', () { + expect( + constructor([ + parameter('foo').toDefault(), + ]).attachTo(animal), + equalsSource(r''' + Animal([foo]); + '''), + ); + }); + + test('should emit a constructor with a field formal parameter', () { + expect( + constructor([ + parameter('foo').toField(), + ]).attachTo(animal), + equalsSource(r''' + Animal(this.foo); + '''), + ); + }); + + test('should emit a constructor with a named parameter', () { + expect( + new ConstructorBuilder(animal)..addNamedParameter(parameter('foo')), + equalsSource(r''' + Animal({foo}); + '''), + ); + }); + + test('should emit a constructor with a named parameter with a value', () { + expect( + new ConstructorBuilder(animal) + ..addNamedParameter(parameter('foo', [literal(true)])), + equalsSource(r''' + Animal({foo: true}); + '''), + ); + }); + }); +} diff --git a/test/expression_test.dart b/test/expression_test.dart index 3477faa..18048a4 100644 --- a/test/expression_test.dart +++ b/test/expression_test.dart @@ -1,5 +1,5 @@ import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/src/builders/expression/expression.dart'; +import 'package:code_builder/src/builders/expression.dart'; import 'package:code_builder/testing/equals_source.dart'; import 'package:test/test.dart'; @@ -68,5 +68,93 @@ void main() { var fancy = true; ''')); }); + + group('asIf', () { + test('should emit a single if-block', () { + expect(literal(true).asIf(), equalsSource(r''' + if (true) {} + ''')); + }); + + test('should emit an if-else block', () { + expect(literal(true).asIf().andElse(literal(false)), equalsSource(r''' + if (true) { + + } + else if (false) { + + } + ''')); + }); + + test('should emit an if-else-if-else-else block', () { + expect( + literal(1) + .equals(literal(2)) + .asIf() + .andElse(literal(2).equals(literal(3))) + .andElse(literal(3).equals(literal(4))) + .andElse(), + equalsSource(r''' + if (1 == 2) { + + } else if (2 == 3) { + + } else if (3 == 4) { + + } else { + + } + ''')); + }); + }); + + test('asReturn should emit a return statement', () { + expect(literal(true).asReturn(), equalsSource(r''' + return true; + ''')); + }); + + test('assign should emit an asssignment expression', () { + expect(literal(true).assign('fancy'), equalsSource(r''' + fancy = true + ''')); + }); + + test('asStatement should emit an expression as a statement', () { + expect(literal(true).asStatement(), equalsSource(r''' + true; + ''')); + }); + + test('equals should compare two expressions', () { + expect(literal(true).equals(literal(true)), equalsSource(r''' + true == true + ''')); + }); + + test('not equals should compare two expressions', () { + expect(literal(true).notEquals(literal(true)), equalsSource(r''' + true != true + ''')); + }); + + test('not should negate an expression', () { + expect(literal(true).not(), equalsSource(r''' + !true + ''')); + }); + + test('parentheses should wrap an expression', () { + expect(literal(true).parentheses(), equalsSource(r''' + (true) + ''')); + }); + + test('+ should emit the sum two expressions', () { + expect(literal(1) + literal(2), equalsSource(r''' + 1 + 2 + ''')); + }); }); } diff --git a/test/integration_test.dart b/test/integration_test.dart deleted file mode 100644 index f7565a8..0000000 --- a/test/integration_test.dart +++ /dev/null @@ -1,151 +0,0 @@ -// 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. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -// Closely mirrors the API you would need to do dependency injection :) -void main() { - test('Emits a complex generated file', () { - var clazz = - new ClassBuilder(r'Injector', implement: [new TypeBuilder('App')]) - ..addField(new FieldBuilder.isFinal( - '_module', - type: const TypeBuilder('Module'), - )) - ..addConstructor(new ConstructorBuilder() - ..addParameter(new ParameterBuilder('_module', field: true))) - ..addMethod( - new MethodBuilder( - name: 'getThing', - returns: const TypeBuilder('Thing'), - ) - ..addAnnotation(atOverride) - ..setExpression(new ExpressionBuilder.invokeNew( - const TypeBuilder('Thing'), - positional: [ - new ExpressionBuilder.invoke('_module.getDep1'), - new ExpressionBuilder.invoke('_module.getDep2'), - ], - )), - ); - var lib = new LibraryBuilder() - ..addDirective( - new ImportBuilder('app.dart'), - ) - ..addDeclaration(clazz); - expect( - lib, - equalsSource( - r''' - import 'app.dart'; - - class Injector implements App { - final Module _module; - - Injector(this._module); - - @override - Thing getThing() => new Thing(_module.getDep1(), _module.getDep2()); - } - ''', - ), - ); - }); - - test('Emits a complex generated file with scoping applied', () { - var clazz = new ClassBuilder( - r'Injector', - implement: [new TypeBuilder('App', importFrom: 'package:app/app.dart')], - ) - ..addField(new FieldBuilder.isFinal( - '_module', - type: const TypeBuilder( - 'Module', - importFrom: 'package:app/app.dart', - ), - )) - ..addConstructor(new ConstructorBuilder() - ..addParameter(new ParameterBuilder('_module', field: true))) - ..addMethod( - new MethodBuilder( - name: 'getThing', - returns: const TypeBuilder( - 'Thing', - importFrom: 'package:app/thing.dart', - ), - ) - ..addAnnotation(atOverride) - ..setExpression(new ExpressionBuilder.invokeNew( - const TypeBuilder( - 'Thing', - importFrom: 'package:app/thing.dart', - ), - positional: [ - new ExpressionBuilder.invoke('_module.getDep1'), - new ExpressionBuilder.invoke('_module.getDep2'), - ], - )), - ); - var lib = new LibraryBuilder.scope()..addDeclaration(clazz); - expect( - lib, - equalsSource( - r''' - import 'package:app/app.dart' as _i1; - import 'dart:core' as _i2; - import 'package:app/thing.dart' as _i3; - - - class Injector implements _i1.App { - final _i1.Module _module; - - Injector(this._module); - - @_i2.override - _i3.Thing getThing() => new _i3.Thing(_module.getDep1(), _module.getDep2()); - } - ''', - ), - ); - }); - - test('Emits a complex generated file with conflicting imports scoped', () { - var lib = new LibraryBuilder.scope() - ..addDeclaration(new MethodBuilder( - name: 'doThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - )) - ..addDeclaration(new MethodBuilder( - name: 'doOtherThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/alternative.dart', - )) - ..addParameter(new ParameterBuilder( - 'thing', - type: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - ))); - expect( - lib, - equalsSource( - r''' - import 'package:thing/thing.dart' as _i1; - import 'package:thing/alternative.dart' as _i2; - - _i1.Thing doThing() {} - _i2.Thing doOtherThing(_i1.Thing thing) {} - ''', - ), - ); - }); -} diff --git a/test/parameter_test.dart b/test/parameter_test.dart new file mode 100644 index 0000000..50630b5 --- /dev/null +++ b/test/parameter_test.dart @@ -0,0 +1,40 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a simple untyped parameter', () { + expect( + parameter('foo'), + equalsSource('foo'), + ); + }); + + test('should emit a simple typed parameter', () { + expect( + parameter('foo', [type('String')]), + equalsSource('String foo'), + ); + }); + + test('should emit a field-formal parameter', () { + expect( + parameter('foo').toField(), + equalsSource('this.foo'), + ); + }); + + test('should emit a parameter with a default value', () { + expect( + parameter('foo', [literal(true)]), + equalsSource('foo = true'), + ); + }); + + test('shuld emit a field-formal parameter with a default value', () { + expect( + parameter('foo', [literal(true)]).toField(), + equalsSource('this.foo = true'), + ); + }); +} diff --git a/test/reference_test.dart b/test/reference_test.dart new file mode 100644 index 0000000..b68bc3f --- /dev/null +++ b/test/reference_test.dart @@ -0,0 +1,64 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing/equals_source.dart'; +import 'package:test/test.dart'; + +void main() { + group('reference', () { + test('should emit an identifier', () { + expect(reference('fooBar'), equalsSource(r'fooBar')); + }); + + test('should emit an expression when used as one', () { + expect(reference('a').equals(reference('b')), equalsSource(r''' + a == b + ''')); + }); + + test('should emit a scoped identifier', () { + expect( + reference('fooBar', 'package:foo_bar/foo_bar.dart'), + equalsSource( + r''' + foo_bar.fooBar + ''', + scope: simpleNameScope), + ); + }); + + test('should emit a method invocation on the reference', () { + expect( + reference('print').call(), + equalsSource(r''' + print() + '''), + ); + }); + + test('should emit a method invocation on a method of the refernece', () { + expect( + reference('foo').invoke('bar'), + equalsSource(r''' + foo.bar() + '''), + ); + }); + + test('should emit a method invocation with named args', () { + expect( + reference('foo').callWith(named: {'bar': literal(true)}), + equalsSource(r''' + foo(bar: true) + '''), + ); + }); + + test('should emit a method invocation on a method with named args', () { + expect( + reference('foo').invokeWith('bar', named: {'baz': literal(true)}), + equalsSource(r''' + foo.bar(baz: true) + '''), + ); + }); + }); +} diff --git a/test/scope_test.dart b/test/scope_test.dart index a75532a..4851c3f 100644 --- a/test/scope_test.dart +++ b/test/scope_test.dart @@ -10,7 +10,7 @@ void main() { group('Identity scope', () { Scope scope; - setUp(() => scope = const Scope.identity()); + setUp(() => scope = Scope.identity); test('should do nothing', () { var identifiers = [ @@ -61,7 +61,7 @@ void main() { ], ); }); - }); + }, skip: ''); group('Incrementing scope', () { Scope scope; @@ -92,5 +92,5 @@ void main() { ], ); }); - }); + }, skip: ''); } diff --git a/test/type_test.dart b/test/type_test.dart new file mode 100644 index 0000000..c8bbf1d --- /dev/null +++ b/test/type_test.dart @@ -0,0 +1,31 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing/equals_source.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a type', () { + expect(reference('List'), equalsSource(r'List')); + }); + + test('should emit a type with a generic type parameter', () { + expect( + reference('List').generic([reference('String')]), + equalsSource(r''' + List + '''), + ); + }); + + test('should emit a type with prefixed generic types', () { + expect( + reference('SuperList', 'package:super/list.dart').generic([ + reference('Super', 'package:super/super.dart'), + ]), + equalsSource( + r''' + list.SuperList + ''', + scope: simpleNameScope), + ); + }); +} From c70a7e0c64b667126de59b7a834248b83473607c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 12:55:19 -0700 Subject: [PATCH 03/19] Start on new v1 --- lib/code_builder.dart | 89 +-------- lib/dart/core.dart | 142 ------------- lib/src/analyzer_patch.dart | 65 ------ lib/src/builders/annotation.dart | 43 +++- lib/src/builders/class.dart | 56 +++--- lib/src/builders/class/constructor.dart | 141 ------------- lib/src/builders/expression.dart | 159 +++------------ lib/src/builders/expression/assert.dart | 20 -- lib/src/builders/expression/assign.dart | 76 ------- lib/src/builders/expression/binary.dart | 21 -- lib/src/builders/expression/if.dart | 91 --------- lib/src/builders/expression/invocation.dart | 76 ------- lib/src/builders/expression/literal.dart | 56 ------ lib/src/builders/expression/mixin.dart | 118 ----------- lib/src/builders/expression/negate.dart | 18 -- lib/src/builders/expression/parentheses.dart | 19 -- lib/src/builders/expression/return.dart | 19 -- lib/src/builders/parameter.dart | 189 ------------------ lib/src/builders/reference.dart | 59 ++---- lib/src/builders/shared.dart | 59 +++++- .../builders/shared/annotations_mixin.dart | 42 ---- lib/src/builders/shared/parameters_mixin.dart | 95 --------- lib/src/builders/shared/statements_mixin.dart | 45 ----- lib/src/builders/statement.dart | 36 +++- lib/src/builders/type.dart | 49 ----- lib/src/pretty_printer.dart | 106 ---------- lib/src/scope.dart | 83 -------- lib/src/tokens.dart | 125 ------------ lib/testing.dart | 1 - lib/testing/equals_source.dart | 122 ----------- test/builders/class_test.dart | 6 + test/builders/shared_test.dart | 16 ++ test/class_test.dart | 129 ------------ test/expression_test.dart | 160 --------------- test/parameter_test.dart | 40 ---- test/reference_test.dart | 64 ------ test/scope_test.dart | 96 --------- test/type_test.dart | 31 --- tool/travis.sh | 28 --- 39 files changed, 206 insertions(+), 2584 deletions(-) delete mode 100644 lib/dart/core.dart delete mode 100644 lib/src/analyzer_patch.dart delete mode 100644 lib/src/builders/class/constructor.dart delete mode 100644 lib/src/builders/expression/assert.dart delete mode 100644 lib/src/builders/expression/assign.dart delete mode 100644 lib/src/builders/expression/binary.dart delete mode 100644 lib/src/builders/expression/if.dart delete mode 100644 lib/src/builders/expression/invocation.dart delete mode 100644 lib/src/builders/expression/literal.dart delete mode 100644 lib/src/builders/expression/mixin.dart delete mode 100644 lib/src/builders/expression/negate.dart delete mode 100644 lib/src/builders/expression/parentheses.dart delete mode 100644 lib/src/builders/expression/return.dart delete mode 100644 lib/src/builders/parameter.dart delete mode 100644 lib/src/builders/shared/annotations_mixin.dart delete mode 100644 lib/src/builders/shared/parameters_mixin.dart delete mode 100644 lib/src/builders/shared/statements_mixin.dart delete mode 100644 lib/src/builders/type.dart delete mode 100644 lib/src/pretty_printer.dart delete mode 100644 lib/src/scope.dart delete mode 100644 lib/testing.dart delete mode 100644 lib/testing/equals_source.dart create mode 100644 test/builders/class_test.dart create mode 100644 test/builders/shared_test.dart delete mode 100644 test/class_test.dart delete mode 100644 test/expression_test.dart delete mode 100644 test/parameter_test.dart delete mode 100644 test/reference_test.dart delete mode 100644 test/scope_test.dart delete mode 100644 test/type_test.dart delete mode 100755 tool/travis.sh diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 1311fa6..7c8f4b2 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -1,87 +1,4 @@ -// 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. - -/// Code builder is a fluent Dart API for generating valid Dart source code. -/// -/// Generally speaking, code generation usually is done through a series of -/// string concatenation which results in messy and sometimes invalid code that -/// is not easily readable. -/// -/// Code builder uses the `analyzer` package to create real Dart language ASTs, -/// which, while not guaranteed to be correct, always follows the analyzer's -/// own understood format. -/// -/// Code builder also adds a more narrow and user-friendly API. For example -/// creating a class with a method is an easy affair: -/// new ClassBuilder('Animal', extends: 'Organism') -/// ..addMethod(new MethodBuilder.returnVoid('eat') -/// ..setExpression(new ExpressionBuilder.invoke('print', -/// positional: [new LiteralString('Yum!')]))); -/// -/// Outputs: -/// class Animal extends Organism { -/// void eat() => print('Yum!'); -/// } -/// -/// This package is in development and APIs are subject to frequent change. See -/// the `README.md` and `CONTRIBUTING.md` for more information. -library code_builder; - -import 'package:analyzer/analyzer.dart'; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:meta/meta.dart'; - -import 'src/analyzer_patch.dart'; -import 'src/tokens.dart'; - -export 'src/builders/annotation.dart' show annotation, AnnotationBuilder; -export 'src/builders/class.dart' - show - clazz, - constructor, - constructorNamed, - initializer, - ClassBuilder, - ConstructorBuilder; -export 'src/builders/expression.dart' show ExpressionBuilder, literal; -export 'src/builders/parameter.dart' show parameter, ParameterBuilder; -export 'src/builders/reference.dart' show reference; +export 'src/builders/annotation.dart' show AnnotationBuilder; +export 'src/builders/class.dart' show clazz, ClassBuilder; +export 'src/builders/expression.dart' show ExpressionBuilder; export 'src/builders/statement.dart' show StatementBuilder; -export 'src/builders/type.dart' show type, TypeBuilder; - -part 'src/pretty_printer.dart'; -part 'src/scope.dart'; - -final DartFormatter _dartfmt = new DartFormatter(); - -// Simplifies some of the builders by having a mutable node we clone from. -/// Returns [source] formatted by `dartfmt`. -@visibleForTesting -String dartfmt(String source) { - try { - return _dartfmt.format(source); - } on FormatterException catch (_) { - return _dartfmt.formatStatement(source); - } -} - -SimpleIdentifier _stringIdentifier(String s) => - new SimpleIdentifier(stringToken(s)); - -/// A builder that emits a _specific_ Dart language [AstNode]. -abstract class AstBuilder { - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// Uses [scope] to output an AST re-written to use appropriate prefixes. - AstNode buildAst([Scope scope = Scope.identity]); -} - -/// Base class for building and emitting a Dart language [AstNode]. -abstract class CodeBuilder { - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// Uses [scope] to output an AST re-written to use appropriate prefixes. - A build([Scope scope = Scope.identity]); -} diff --git a/lib/dart/core.dart b/lib/dart/core.dart deleted file mode 100644 index 2561a1c..0000000 --- a/lib/dart/core.dart +++ /dev/null @@ -1,142 +0,0 @@ -// 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. - -/// Contains references to the `dart:core` library for use in code generation. -/// -/// This library is currently *experimental*, and is subject to change; it -/// currently is manually written but there might be a strong use case for this -/// to be automatically generated (at least partially) in the near future. -library code_builder.dart.core; - -import 'package:code_builder/src/builders/annotation.dart'; -import 'package:code_builder/src/builders/reference.dart'; -import 'package:code_builder/src/builders/type.dart'; - -/// An alias for `new AnnotationBuilder('override')`. -final ReferenceBuilder atOverride = reference( - 'override', - 'dart:core', -); - -/// A reference to the `print` method. -final ReferenceBuilder printMethod = reference( - 'print', - 'dart:core', -); - -/// An alias for `new TypeBuilder('bool')`. -final TypeBuilder typeBool = new TypeBuilder( - 'bool', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('DateTime')`. -final TypeBuilder typeDateTime = new TypeBuilder( - 'DateTime', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('double')`. -final TypeBuilder typeDouble = new TypeBuilder( - 'double', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('Duration')`. -final TypeBuilder typeDuration = new TypeBuilder( - 'Duration', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('Expando')`. -final TypeBuilder typeExpando = new TypeBuilder( - 'Expando', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Function')`. -final TypeBuilder typeFunction = new TypeBuilder( - 'Function', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('int')`. -final TypeBuilder typeInt = new TypeBuilder( - 'int', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Iterable')`. -final TypeBuilder typeIterable = new TypeBuilder( - 'Iterable', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('List')`. -final TypeBuilder typeList = new TypeBuilder( - 'List', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Map')`. -final TypeBuilder typeMap = new TypeBuilder( - 'Map', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Null')`. -final TypeBuilder typeNull = new TypeBuilder( - 'Null', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('num')`. -final TypeBuilder typeNum = new TypeBuilder( - 'num', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Object')`. -final TypeBuilder typeObject = new TypeBuilder( - 'Object', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Set')`. -final TypeBuilder typeSet = new TypeBuilder( - 'Set', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('String')`. -final TypeBuilder typeString = new TypeBuilder( - 'String', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Symbol')`. -final TypeBuilder typeSymbol = new TypeBuilder( - 'Symbol', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Type')`. -final TypeBuilder typeType = new TypeBuilder( - 'Type', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Uri')`. -final TypeBuilder typeUri = new TypeBuilder( - 'Uri', - importFrom: 'dart:core', -); - -/// Creates either a `@deprecated` or `@Deprecated('Message')` annotation. -AnnotationBuilder atDeprecated([String message]) { - if (message == null) { - return reference('deprecated', 'dart:core'); - } - throw new UnimplementedError(); -} diff --git a/lib/src/analyzer_patch.dart b/lib/src/analyzer_patch.dart deleted file mode 100644 index 5b27d77..0000000 --- a/lib/src/analyzer_patch.dart +++ /dev/null @@ -1,65 +0,0 @@ -// 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. - -import 'package:analyzer/src/generated/java_core.dart'; - -/// Implements both old-API [PrintWriter] and new-API [StringBuffer]. -/// -/// This makes it easier to re-use our `pretty_printer` until analyzer updates. -class PrintBuffer implements PrintWriter, StringBuffer { - final StringBuffer _impl = new StringBuffer(); - - @override - void clear() {} - - @override - bool get isEmpty => _impl.isEmpty; - - @override - bool get isNotEmpty => _impl.isNotEmpty; - - @override - int get length => _impl.length; - - @override - void newLine() { - _impl.writeln(); - } - - @override - void print(x) { - _impl.write(x); - } - - @override - void printf(String fmt, List args) => throw new UnimplementedError(); - - @override - void println(String s) { - _impl.writeln(s); - } - - @override - void write(Object obj) { - _impl.write(obj); - } - - @override - void writeAll(Iterable objects, [String separator = ""]) { - _impl.writeAll(objects); - } - - @override - void writeCharCode(int charCode) { - _impl.writeCharCode(charCode); - } - - @override - void writeln([Object obj = ""]) { - _impl.writeln(obj); - } - - @override - String toString() => _impl.toString(); -} diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index 013f086..7dca1d1 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -1,14 +1,41 @@ import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/class.dart'; import 'package:code_builder/src/builders/shared.dart'; -/// Returns a new [AnnotationBuilder] referencing [identifier]. -AnnotationBuilder annotation(String identifier, [String importFrom]) { - return reference(identifier, importFrom); +/// Lazily builds an [Annotation] AST when [buildAnnotation] is invoked. +abstract class AnnotationBuilder implements ValidClassMember { + /// Returns an [Annotation] AST representing the builder. + Annotation buildAnnotation([Scope scope]); } -/// Builds an [Annotation] AST. -abstract class AnnotationBuilder implements AstBuilder, ValidParameterMember { - /// Returns as an [Annotation] AST. - Annotation buildAnnotation([Scope scope = Scope.identity]); +/// An [AstBuilder] that can be annotated with [AnnotationBuilder]. +abstract class HasAnnotations implements AstBuilder { + /// Adds [annotation] to the builder. + void addAnnotation(AnnotationBuilder annotation); + + /// Adds [annotations] to the builder. + void addAnnotations(Iterable annotations); +} + +/// Implements [HasAnnotations]. +abstract class HasAnnotationsMixin extends HasAnnotations { + final List _annotations = []; + + @override + void addAnnotation(AnnotationBuilder annotation) { + _annotations.add(annotation); + } + + @override + void addAnnotations(Iterable annotations) { + _annotations.addAll(annotations); + } + + /// Clones all annotations to [clone]. + void cloneAnnotationsTo(HasAnnotations clone) { + clone.addAnnotations(_annotations); + } + + /// Returns a [List] of all built [Annotation]s. + List buildAnnotations([Scope scope]) => _annotations.map/**/((a) => a.buildAnnotation(scope)).toList(); } diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index e42df8a..7a0945d 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -1,48 +1,54 @@ -library code_builder.src.builders.clazz; - import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/shared.dart'; -import 'package:code_builder/src/tokens.dart'; - -part 'class/constructor.dart'; -/// Returns a new [ClassBuilder]. -/// -/// Shorthand for `new ClassBuilder` for more functional aficionados. -ClassBuilder clazz(String name, [Iterable members = const []]) { +/// A more short-hand way of constructing a [ClassBuilder]. +ClassBuilder clazz( + String name, [ + Iterable members = const [], +]) { final clazz = new ClassBuilder(name); for (final member in members) { if (member is AnnotationBuilder) { clazz.addAnnotation(member); + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); } } return clazz; } -/// Builds a [ClassDeclaration] AST. -class ClassBuilder implements AstBuilder { - final List _annotations = []; +/// Lazily builds an [ClassDeclaration] AST when [buildClass] is invoked. +abstract class ClassBuilder + implements AstBuilder, HasAnnotations { + /// Returns a new [ClassBuilder] with [name]. + factory ClassBuilder(String name) = _ClassBuilderImpl; + + /// Returns an [ClassDeclaration] AST representing the builder. + ClassDeclaration buildClass([Scope scope]); +} + +/// A marker interface for an AST that could be added to [ClassBuilder]. +abstract class ValidClassMember implements AstBuilder {} + +class _ClassBuilderImpl extends Object + with HasAnnotationsMixin + implements ClassBuilder { final String _name; - /// Create a new `class`. - ClassBuilder(this._name); + _ClassBuilderImpl(this._name); - /// Adds [annotation]. - void addAnnotation(AnnotationBuilder annotation) { - _annotations.add(annotation); - } + @override + ClassDeclaration buildAst([Scope scope]) => buildClass(scope); @override - AstNode buildAst([Scope scope = Scope.identity]) { + ClassDeclaration buildClass([Scope scope]) { return new ClassDeclaration( null, - _annotations - .map/**/((a) => a.buildAnnotation(scope)) - .toList(), + buildAnnotations(scope), + null, null, - $class, - new SimpleIdentifier(stringToken(_name)), + stringIdentifier(_name), null, null, null, diff --git a/lib/src/builders/class/constructor.dart b/lib/src/builders/class/constructor.dart deleted file mode 100644 index f1f3f74..0000000 --- a/lib/src/builders/class/constructor.dart +++ /dev/null @@ -1,141 +0,0 @@ -part of code_builder.src.builders.clazz; - -void _addMembers( - ConstructorBuilder constructor, - Iterable members, -) { - for (final member in members) { - if (member is StatementBuilder) { - constructor.addStatement(member); - } else if (member is ConstructorInitializerBuilder) { - constructor.addInitializer(member); - } else if (member is ParameterBuilder) { - constructor.addPositionalParameter(member); - } - } -} - -/// Returns a new unnamed constructor with [members]. -ConstructorBuilder constructor([ - Iterable members = const [], -]) { - final constructor = new _ConstructorBuilderImpl._detached(null); - _addMembers(constructor, members); - return constructor; -} - -/// Returns a new named constructor with [members]. -ConstructorBuilder constructorNamed( - String name, [ - Iterable members = const [], -]) { - final constructor = new _ConstructorBuilderImpl._detached(name); - _addMembers(constructor, members); - return constructor; -} - -/// Return a new initializer of [field] to [expression]. -ConstructorInitializerBuilder initializer( - String field, ExpressionBuilder expression) { - return new ConstructorInitializerBuilder._(field, expression); -} - -/// A detached initializer statement for a [ConstructorBuilder]. -/// -/// See [initializer] and [ExpressionBuilder.asInitializer]. -class ConstructorInitializerBuilder implements ValidConstructorMember { - final String _field; - final ExpressionBuilder _initializeTo; - - ConstructorInitializerBuilder._(this._field, this._initializeTo); - - /// Returns a new [ConstructorInitializer] AST. - ConstructorInitializer buildConstructorInitializer( - [Scope scope = Scope.identity]) => - buildAst(scope); - - @override - AstNode buildAst([Scope scope = Scope.identity]) { - return new ConstructorFieldInitializer( - $this, - $period, - new SimpleIdentifier(stringToken(_field)), - $equals, - _initializeTo.buildExpression(scope), - ); - } -} - -/// Builds a [ConstructorDeclaration] AST. -abstract class ConstructorBuilder - implements AstBuilder, HasAnnotations, HasParameters, HasStatements { - /// Create a new [ConstructorBuilder] for a `class`, optionally named. - factory ConstructorBuilder(ClassBuilder clazz, [String name]) = - _ConstructorBuilderImpl; - - /// Adds an [initializer]. - void addInitializer(ConstructorInitializerBuilder initializer); - - /// Returns a new [ConstructorBuilder] attached to [clazz]. - ConstructorBuilder attachTo(ClassBuilder clazz); -} - -/// Builds a [ConstructorDeclaration] AST. -class _ConstructorBuilderImpl extends Object - with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin - implements ConstructorBuilder { - final ClassBuilder _clazz; - final List _initializers = - []; - final String _name; - - _ConstructorBuilderImpl(this._clazz, [this._name]); - - // Internal: Allows using the constructor() factory inside of clazz(). - _ConstructorBuilderImpl._detached([this._name]) : this._clazz = null; - - @override - ConstructorBuilder attachTo(ClassBuilder clazz) { - final constructor = new ConstructorBuilder(clazz, _name); - _initializers.forEach(constructor.addInitializer); - cloneStatementsTo(constructor); - cloneParametersTo(constructor); - return constructor; - } - - @override - void addInitializer(ConstructorInitializerBuilder initializer) { - _initializers.add(initializer); - } - - @override - AstNode buildAst([Scope scope = Scope.identity]) { - return new ConstructorDeclaration( - null, - toAnnotationAsts(scope), - null, - null, - null, - new SimpleIdentifier(stringToken(_clazz._name)), - _name != null ? $period : null, - _name != null ? new SimpleIdentifier(stringToken(_name)) : null, - toFormalParameterList(scope), - null, - _initializers - .map/**/( - (i) => i.buildConstructorInitializer(scope)) - .toList(), - null, - !hasStatements - ? new EmptyFunctionBody($semicolon) - : new BlockFunctionBody( - null, - null, - new Block( - $openCurly, - toStatementAsts(scope), - $closeCurly, - )), - ); - } -} diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 410066f..f1eee7e 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -1,143 +1,34 @@ -library code_builder.src.builders.expression; - import 'package:analyzer/analyzer.dart'; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:code_builder/code_builder.dart' show Scope; -import 'package:code_builder/src/tokens.dart'; -import 'package:code_builder/src/builders/class.dart'; -import 'package:code_builder/src/builders/statement.dart'; import 'package:code_builder/src/builders/shared.dart'; -import 'package:code_builder/src/builders/type.dart'; - -part 'expression/assert.dart'; -part 'expression/assign.dart'; -part 'expression/binary.dart'; -part 'expression/if.dart'; -part 'expression/invocation.dart'; -part 'expression/literal.dart'; -part 'expression/mixin.dart'; -part 'expression/negate.dart'; -part 'expression/parentheses.dart'; -part 'expression/return.dart'; - -/// Builds an [Expression] AST. -abstract class ExpressionBuilder - implements StatementBuilder, ValidParameterMember { - /// Returns _as_ a statement that `asserts` the expression. - StatementBuilder asAssert(); - - /// Returns _as_ an expression that assigns to an existing [variable]. - /// - /// __Example use__: - /// literalTrue.assign('foo') // Outputs "foo = true" - ExpressionBuilder assign(String variable); - - /// Returns _as_ a statement that assigns to a new `const` [variable]. - /// - /// __Example use__: - /// literalTrue.asConst('foo') // Outputs "const foo = true;" - StatementBuilder asConst(String variable, [TypeBuilder type]); - - /// Returns _as_ a statement that assigns to a new `final` [variable]. - /// - /// __Example use__: - /// literalTrue.asFinal('foo') // Outputs "final foo = true;" - StatementBuilder asFinal(String variable, [TypeBuilder type]); - - /// Returns _as_ a statement that assigns to a new [variable]. - /// - /// __Example use__: - /// literalTrue.asVar('foo') // Outputs "var foo = true;" - StatementBuilder asVar(String variable); - - /// Returns _as_ a statement that builds an `if` statement. - /// - /// __Example use__: - /// literalTrue.asIf() // Outputs "if (true) { ... }" - IfStatementBuilder asIf(); - - /// Returns _as_ a constructor initializer setting [field] - ConstructorInitializerBuilder asInitializer(String field); - /// Returns _as_ a statement that `return`s this expression. - /// - /// __Example use__: - /// literalTrue.asReturn() // Outputs "return true;" - StatementBuilder asReturn(); - - /// Returns _as_ a statement. - /// - /// **NOTE**: An [ExpressionBuilder] _is_ a [StatementBuilder]; this cast - /// is only necessary if you explicitly want [buildAst] to return a - /// [Statement] object, not an [Expression]. - /// - /// __Example use__: - /// literalTrue.asStatement() // Outputs "true;" - StatementBuilder asStatement() => new _AsStatement(this); - - /// Returns _as_ an expression calling itself. - /// - /// __Example use__: - /// reference('foo').call() // Outputs "foo()" - ExpressionSelfInvocationBuilder call([Iterable arguments]); - - /// Returns _as_ an expression calling itself. - /// - /// Unlike [call], may specify either [named] and/or [positional] arguments. - ExpressionSelfInvocationBuilder callWith({ - Map named, - Iterable positional, - }); - - /// Returns _as_ an expression accessing and calling [method]. - /// - /// __Example use__: - /// literalTrue.invoke('toString') // Outputs true.toString() - ExpressionInvocationBuilder invoke( - String method, [ - Iterable arguments, - ]); - - /// Returns _as_ an expression accessing and calling [method]. - /// - /// Unlike [invoke], may specify either [named] and/or [positional] arguments. - ExpressionInvocationBuilder invokeWith( - String method, { - Map named, - Iterable positional, - }); - - /// Returns _as_ an expression using the equality operator on [other]. - /// - /// __Example use__: - /// literalTrue.equals(literalTrue) // Outputs "true == true" - ExpressionBuilder equals(ExpressionBuilder other); - - /// Returns _as_ an expression using the not-equals operator on [other]. - /// - /// __Example use__: - /// literalTrue.notEquals(literalTrue) // Outputs "true != true" - ExpressionBuilder notEquals(ExpressionBuilder other); +/// Lazily builds an [Expression] AST when [buildExpression] is invoked. +abstract class ExpressionBuilder implements AstBuilder { + /// Returns an [Expression] AST representing the builder. + Expression buildExpression([Scope scope]); +} - /// Returns _as_ an expression negating this one. - /// - /// __Example use__: - /// literalTrue.not() // Outputs "!true" - ExpressionBuilder not(); +/// An [AstBuilder] that can add [ExpressionBuilder]. +abstract class HasExpressions implements AstBuilder { + final List _expressions = []; - /// Returns _as_ an expression summing this and [other]. - ExpressionBuilder operator +(ExpressionBuilder other); + /// Adds [expression] to the builder. + void addExpression(ExpressionBuilder expression) { + _expressions.add(expression); + } - /// Returns _as_ an expression wrapped in parentheses. - /// - /// __Example use__: - /// literalTrue.parentheses() // Outputs "(true)" - ExpressionBuilder parentheses(); + /// Adds [expressions] to the builder. + void addExpressions(Iterable expressions) { + _expressions.addAll(expressions); + } +} - /// Returns as an [Expression] AST. - Expression buildExpression([Scope scope = Scope.identity]); +/// Implements [HasExpressions]. +abstract class HasExpressionsMixin extends HasExpressions { + /// Clones all expressions to [clone]. + void cloneExpressionsTo(HasExpressions clone) { + clone.addExpressions(_expressions); + } - /// Returns as a [Statement] AST. - @override - Statement buildStatement([Scope scope = Scope.identity]); + /// Returns a [List] of all built [Expression]s. + List buildExpressions([Scope scope]) => _expressions.map/**/((e) => e.buildExpression(scope)).toList(); } diff --git a/lib/src/builders/expression/assert.dart b/lib/src/builders/expression/assert.dart deleted file mode 100644 index e6a9ee2..0000000 --- a/lib/src/builders/expression/assert.dart +++ /dev/null @@ -1,20 +0,0 @@ -part of code_builder.src.builders.expression; - -class _AsAssert extends StatementBuilder { - final ExpressionBuilder _expression; - - _AsAssert(this._expression); - - @override - Statement buildStatement([Scope scope = Scope.identity]) { - return new AssertStatement( - $assert, - $openParen, - _expression.buildExpression(scope), - null, - null, - $closeParen, - $semicolon, - ); - } -} diff --git a/lib/src/builders/expression/assign.dart b/lib/src/builders/expression/assign.dart deleted file mode 100644 index 7dbb536..0000000 --- a/lib/src/builders/expression/assign.dart +++ /dev/null @@ -1,76 +0,0 @@ -part of code_builder.src.builders.expression; - -class _AsAssign extends ExpressionBuilderMixin { - final ExpressionBuilder _expression; - final String _name; - - _AsAssign(this._expression, this._name); - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - final name = new SimpleIdentifier(stringToken(_name)); - return new AssignmentExpression( - name, - $equals, - _expression.buildExpression(scope), - ); - } -} - -enum _AsAssignType { - asConst, - asFinal, - asVar, -} - -class _AsAssignStatement extends StatementBuilder { - final ExpressionBuilder _expression; - final String _name; - final _AsAssignType _keyword; - final TypeBuilder _type; - - _AsAssignStatement( - this._expression, - this._name, { - _AsAssignType keyword, - TypeBuilder type, - }) - : _keyword = keyword, - _type = type; - - @override - Statement buildStatement([Scope scope = Scope.identity]) { - final name = new SimpleIdentifier(stringToken(_name)); - Token token; - switch (_keyword) { - case _AsAssignType.asConst: - token = $const; - break; - case _AsAssignType.asFinal: - token = $final; - break; - case _AsAssignType.asVar: - token = $var; - break; - } - return new VariableDeclarationStatement( - new VariableDeclarationList( - null, - null, - token, - _type?.buildType(scope), - [ - new VariableDeclaration( - name, - $equals, - _expression.buildExpression(scope), - ) - ], - ), - $semicolon, - ); - } -} diff --git a/lib/src/builders/expression/binary.dart b/lib/src/builders/expression/binary.dart deleted file mode 100644 index fe3ca82..0000000 --- a/lib/src/builders/expression/binary.dart +++ /dev/null @@ -1,21 +0,0 @@ -part of code_builder.src.builders.expression; - -class _BinaryExpression extends ExpressionBuilderMixin { - final ExpressionBuilder _a; - final ExpressionBuilder _b; - final Token _operator; - - _BinaryExpression(this._a, this._b, this._operator); - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - return new BinaryExpression( - _a.buildExpression(scope), - _operator, - _b.buildExpression(scope), - ); - } -} diff --git a/lib/src/builders/expression/if.dart b/lib/src/builders/expression/if.dart deleted file mode 100644 index 6d0d4c2..0000000 --- a/lib/src/builders/expression/if.dart +++ /dev/null @@ -1,91 +0,0 @@ -part of code_builder.src.builders.expression; - -/// Creates a new builder for creating an `if` statement. -class IfStatementBuilder implements StatementBuilder { - final ExpressionBuilder _condition; - final List _statements = []; - - IfStatementBuilder._(this._condition); - - /// Lazily adds a [statement] builder. - void addStatement(StatementBuilder statement) { - _statements.add(statement); - } - - /// Returns as a builder with `else` added. - ElseStatementBuilder andElse([ExpressionBuilder condition]) { - return new ElseStatementBuilder._(this, condition); - } - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildStatement(scope); - - @override - Statement buildStatement([Scope scope = Scope.identity]) => - _buildElse([this], scope); - - static Statement _buildElse( - List stack, [ - Scope scope = Scope.identity, - ]) { - final pop = stack.removeLast(); - return new IfStatement( - $if, - $openParen, - pop._condition.buildExpression(scope), - $closeParen, - new Block( - $openCurly, - pop._statements - .map/**/((s) => s.buildStatement(scope)) - .toList(), - $closeCurly, - ), - stack.isNotEmpty ? $else : null, - stack.isEmpty - ? null - : stack.length > 1 || stack.last._condition != null - ? _buildElse(stack, scope) - : new Block( - $openCurly, - stack.last._statements - .map/**/((s) => s.buildStatement(scope)) - .toList(), - $closeCurly, - ), - ); - } - - /// Parent if-statement, if any. - IfStatementBuilder get parent => null; - - @override - String toString() => '$runtimeType {$_condition}'; -} - -/// Creates a new builder for creating an `else` statement. -class ElseStatementBuilder extends IfStatementBuilder { - final IfStatementBuilder _parent; - - ElseStatementBuilder._(this._parent, [ExpressionBuilder condition]) - : super._(condition); - - @override - Statement buildStatement([Scope scope = Scope.identity]) { - return IfStatementBuilder._buildElse( - _getChain().toList(), - scope, - ); - } - - Iterable _getChain() sync* { - var builder = this; - while (builder != null) { - yield builder; - builder = builder.parent; - } - } - - @override - IfStatementBuilder get parent => _parent; -} diff --git a/lib/src/builders/expression/invocation.dart b/lib/src/builders/expression/invocation.dart deleted file mode 100644 index e382e9e..0000000 --- a/lib/src/builders/expression/invocation.dart +++ /dev/null @@ -1,76 +0,0 @@ -part of code_builder.src.builders.expression; - -/// Builds an invocation expression AST. -class ExpressionSelfInvocationBuilder extends ExpressionBuilderMixin { - final List _positional; - final Map _named; - final ExpressionBuilder _target; - - ExpressionSelfInvocationBuilder._( - this._target, { - Iterable positional: const [], - Map named: const {}, - }) - : _positional = positional.toList(growable: false), - _named = new Map.from(named); - - ArgumentList _toArgumentList([Scope scope = Scope.identity]) { - final expressions = []; - for (final arg in _positional) { - expressions.add(arg.buildExpression(scope)); - } - for (final name in _named.keys) { - final arg = _named[name]; - expressions.add(new NamedExpression( - new Label( - new SimpleIdentifier( - stringToken(name), - ), - $colon, - ), - arg.buildExpression(scope), - )); - } - return new ArgumentList( - $openParen, - expressions, - $closeParen, - ); - } - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - return new FunctionExpressionInvocation( - _target.buildExpression(scope), - null, - _toArgumentList(scope), - ); - } -} - -/// Builds an invocation expression AST. -class ExpressionInvocationBuilder extends ExpressionSelfInvocationBuilder { - final String _method; - - ExpressionInvocationBuilder._( - this._method, - ExpressionBuilder target, { - Map named, - Iterable positional, - }) - : super._(target, named: named, positional: positional); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - return new MethodInvocation( - _target.buildExpression(scope), - $period, - new SimpleIdentifier(stringToken(_method)), - null, - _toArgumentList(scope), - ); - } -} diff --git a/lib/src/builders/expression/literal.dart b/lib/src/builders/expression/literal.dart deleted file mode 100644 index 620418a..0000000 --- a/lib/src/builders/expression/literal.dart +++ /dev/null @@ -1,56 +0,0 @@ -part of code_builder.src.builders.expression; - -class _LiteralExpression extends ExpressionBuilderMixin { - final Literal _literal; - - _LiteralExpression(this._literal); - - @override - AstNode buildAst([_]) => buildExpression(); - - @override - Expression buildExpression([_]) => _literal; -} - -final Literal _null = new NullLiteral($null); -final Literal _true = new BooleanLiteral($true, true); -final Literal _false = new BooleanLiteral($false, false); - -final ExpressionBuilder _literalNull = new _LiteralExpression(_null); -final ExpressionBuilder _literalTrue = new _LiteralExpression(_true); -final ExpressionBuilder _literalFalse = new _LiteralExpression(_false); - -/// Return a literal [value]. -/// -/// Must either be `null` or a primitive type. -/// -/// __Example use__: -/// literal(null); -/// literal('Hello World'); -/// literal(5); -/// literal(false); -/// -/// **NOTE**: A string literal is automatically wrapped in single-quotes. -/// -/// TODO(matanl): Add support for literal [List] and [Map] of other literals. -ExpressionBuilder literal(value) { - if (value == null) { - return _literalNull; - } - if (value is bool) { - return value ? _literalTrue : _literalFalse; - } - if (value is String) { - return new _LiteralExpression(new SimpleStringLiteral( - stringToken("'$value'"), - value, - )); - } - if (value is int) { - return new _LiteralExpression(new IntegerLiteral( - stringToken(value.toString()), - value, - )); - } - throw new ArgumentError('Invalid type: ${value.runtimeType}'); -} diff --git a/lib/src/builders/expression/mixin.dart b/lib/src/builders/expression/mixin.dart deleted file mode 100644 index 27ea05f..0000000 --- a/lib/src/builders/expression/mixin.dart +++ /dev/null @@ -1,118 +0,0 @@ -part of code_builder.src.builders.expression; - -/// A partial implementation of [ExpressionBuilder] suitable as a mixin. -abstract class ExpressionBuilderMixin implements ExpressionBuilder { - @override - StatementBuilder asAssert() => new _AsAssert(this); - - @override - ExpressionBuilder assign(String variable) => new _AsAssign(this, variable); - - @override - StatementBuilder asConst(String variable, [TypeBuilder type]) => - new _AsAssignStatement(this, variable, - keyword: _AsAssignType.asConst, type: type); - - @override - StatementBuilder asFinal(String variable, [TypeBuilder type]) => - new _AsAssignStatement(this, variable, - keyword: _AsAssignType.asFinal, type: type); - - @override - StatementBuilder asVar(String variable, [TypeBuilder type]) => - new _AsAssignStatement(this, variable, - keyword: _AsAssignType.asVar, type: type); - - @override - StatementBuilder asStatement() => new _AsStatement(this); - - @override - ConstructorInitializerBuilder asInitializer(String field) => - initializer(field, this); - - @override - Statement buildStatement([Scope scope = Scope.identity]) { - return new ExpressionStatement( - buildExpression(scope), - $semicolon, - ); - } - - @override - IfStatementBuilder asIf() => new IfStatementBuilder._(this); - - @override - StatementBuilder asReturn() => new _AsReturn(this); - - @override - ExpressionSelfInvocationBuilder call([ - Iterable arguments, - ]) => - new ExpressionSelfInvocationBuilder._(this, positional: arguments); - - @override - ExpressionSelfInvocationBuilder callWith({ - Map named, - Iterable positional, - }) => - new ExpressionSelfInvocationBuilder._( - this, - positional: positional, - named: named, - ); - - @override - ExpressionInvocationBuilder invoke( - String method, [ - Iterable arguments, - ]) => - new ExpressionInvocationBuilder._(method, this, positional: arguments); - - @override - ExpressionInvocationBuilder invokeWith( - String name, { - Map named, - Iterable positional, - }) => - new ExpressionInvocationBuilder._( - name, - this, - positional: positional, - named: named, - ); - - @override - ExpressionBuilder equals(ExpressionBuilder other) { - return new _BinaryExpression(this, other, $equalsEquals); - } - - @override - ExpressionBuilder notEquals(ExpressionBuilder other) { - return new _BinaryExpression(this, other, $notEquals); - } - - @override - ExpressionBuilder not() => new _NegateExpression(this); - - @override - ExpressionBuilder parentheses() => new _ParenthesesExpression(this); - - @override - ExpressionBuilder operator +(ExpressionBuilder other) { - return new _BinaryExpression( - this, - other, - $plus, - ); - } -} - -class _AsStatement extends StatementBuilder { - final ExpressionBuilder _expression; - - _AsStatement(this._expression); - - @override - Statement buildStatement([Scope scope = Scope.identity]) => - _expression.buildStatement(scope); -} diff --git a/lib/src/builders/expression/negate.dart b/lib/src/builders/expression/negate.dart deleted file mode 100644 index 7267cd9..0000000 --- a/lib/src/builders/expression/negate.dart +++ /dev/null @@ -1,18 +0,0 @@ -part of code_builder.src.builders.expression; - -class _NegateExpression extends ExpressionBuilderMixin { - final ExpressionBuilder _expression; - - _NegateExpression(this._expression); - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - return new PrefixExpression( - $not, - _expression.buildExpression(scope), - ); - } -} diff --git a/lib/src/builders/expression/parentheses.dart b/lib/src/builders/expression/parentheses.dart deleted file mode 100644 index f8bc321..0000000 --- a/lib/src/builders/expression/parentheses.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of code_builder.src.builders.expression; - -class _ParenthesesExpression extends ExpressionBuilderMixin { - final ExpressionBuilder _expression; - - _ParenthesesExpression(this._expression); - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - return new ParenthesizedExpression( - $openParen, - _expression.buildExpression(scope), - $closeParen, - ); - } -} diff --git a/lib/src/builders/expression/return.dart b/lib/src/builders/expression/return.dart deleted file mode 100644 index 88765bb..0000000 --- a/lib/src/builders/expression/return.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of code_builder.src.builders.expression; - -class _AsReturn implements StatementBuilder { - final ExpressionBuilder _expression; - - _AsReturn(this._expression); - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildStatement(scope); - - @override - Statement buildStatement([Scope scope = Scope.identity]) { - return new ReturnStatement( - $return, - _expression.buildExpression(scope), - $semicolon, - ); - } -} diff --git a/lib/src/builders/parameter.dart b/lib/src/builders/parameter.dart deleted file mode 100644 index 0eab028..0000000 --- a/lib/src/builders/parameter.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/src/builders/shared.dart'; -import 'package:code_builder/src/tokens.dart'; - -/// Create a new parameter [name], optionally with [members]. -ParameterBuilder parameter( - String name, [ - Iterable members = const [], -]) { - var parameter = new ParameterBuilder( - name, - members.firstWhere( - (p) => p is TypeBuilder, - orElse: () => null, - ) as TypeBuilder, - ); - final expression = members.firstWhere( - (p) => p is ExpressionBuilder, - orElse: () => null, - ); - for (final member in members) { - if (member is AnnotationBuilder) { - parameter.addAnnotation(member); - } - } - if (expression != null) { - parameter = parameter.toDefault(expression); - } - return parameter; -} - -/// Builds a [FormalParameter] AST. -abstract class ParameterBuilder - implements AstBuilder, HasAnnotations, ValidConstructorMember { - /// Create a new parameter [name], optionally with [type]. - factory ParameterBuilder(String name, [TypeBuilder type]) = - _SimpleParameterBuilder; - - /// Returns a new [FormalParameter] AST as a named parameter. - FormalParameter buildNamed([Scope scope = Scope.identity]); - - /// Returns a new [FormalParameter] AST as a positional parameter. - FormalParameter buildPositional([Scope scope = Scope.identity]); - - /// Returns as a new [ParameterBuilder] with a default [expression] value. - ParameterBuilder toDefault([ExpressionBuilder expression]); - - /// Returns as a new [ParameterBuilder] that assigns to `this.{name}`. - ParameterBuilder toField(); -} - -abstract class _AbstractParameterBuilder extends Object - with HasAnnotationsMixin - implements ParameterBuilder { - final String _name; - final TypeBuilder _type; - - _AbstractParameterBuilder(this._name, this._type); - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildPositional(scope); - - @override - ParameterBuilder toDefault([ExpressionBuilder expression]) { - return new _DefaultParameterBuilder( - this, - expression, - ); - } - - @override - ParameterBuilder toField() { - final field = new _FieldParameterBuilder(_name, _type); - field.addAnnotations(getAnnotations()); - return field; - } -} - -class _DefaultParameterBuilder implements ParameterBuilder { - final _AbstractParameterBuilder _builder; - final ExpressionBuilder _expression; - - _DefaultParameterBuilder(this._builder, this._expression); - - @override - void addAnnotation(AnnotationBuilder annotation) { - _builder.addAnnotation(annotation); - } - - @override - void addAnnotations(Iterable annotations) { - _builder.addAnnotations(annotations); - } - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildPositional(scope); - - @override - FormalParameter buildNamed([Scope scope = Scope.identity]) { - return new DefaultFormalParameter( - _builder.buildPositional(scope), - ParameterKind.NAMED, - _expression != null ? $colon : null, - _expression?.buildExpression(scope), - ); - } - - @override - FormalParameter buildPositional([Scope scope = Scope.identity]) { - return new DefaultFormalParameter( - _builder.buildPositional(scope), - ParameterKind.POSITIONAL, - _expression != null ? $equals : null, - _expression?.buildExpression(scope), - ); - } - - @override - ParameterBuilder toDefault([ExpressionBuilder expression]) { - return new _DefaultParameterBuilder(_builder, expression); - } - - @override - ParameterBuilder toField() { - return new _DefaultParameterBuilder(_builder.toField(), _expression); - } -} - -class _SimpleParameterBuilder extends _AbstractParameterBuilder { - _SimpleParameterBuilder(String name, [TypeBuilder type]) : super(name, type); - - @override - FormalParameter buildPositional([Scope scope = Scope.identity]) { - return new SimpleFormalParameter( - null, - toAnnotationAsts(scope), - null, - _type?.buildType(scope), - new SimpleIdentifier(stringToken(_name)), - ); - } - - @override - FormalParameter buildNamed([Scope scope = Scope.identity]) { - return new DefaultFormalParameter( - buildPositional(scope), - ParameterKind.NAMED, - null, - null, - ); - } -} - -class _FieldParameterBuilder extends _AbstractParameterBuilder { - _FieldParameterBuilder(String name, [TypeBuilder type]) : super(name, type); - - @override - FormalParameter buildPositional([Scope scope = Scope.identity]) { - return new FieldFormalParameter( - null, - toAnnotationAsts(scope), - null, - _type?.buildType(scope), - $this, - $period, - new SimpleIdentifier(stringToken(_name)), - null, - null, - ); - } - - @override - FormalParameter buildNamed([Scope scope = Scope.identity]) { - return new DefaultFormalParameter( - buildPositional(scope), - ParameterKind.NAMED, - null, - null, - ); - } - - @override - ParameterBuilder toField() { - final field = new _FieldParameterBuilder(_name, _type); - field.addAnnotations(getAnnotations()); - return field; - } -} diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index 2d35fb7..b1effde 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -1,60 +1,25 @@ import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart' show Scope; -import 'package:code_builder/src/tokens.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/shared.dart'; -import 'annotation.dart'; -import 'expression.dart'; -import 'type.dart'; - -/// Create a reference to [identifier], optionally where to [importFrom]. -ReferenceBuilder reference(String identifier, [String importFrom]) { - return new ReferenceBuilder._(identifier, importFrom); +/// Creates a reference called [name]. +ReferenceBuilder reference(String name, [String importUri]) { + return new ReferenceBuilder._(name); } -/// A reference to a type, field, variable, etc. -/// -/// [ReferenceBuilder] does _not_ emit an AST itself, but rather is a -/// convenience builder for creating other forms of ASTs. -class ReferenceBuilder extends ExpressionBuilderMixin - implements AnnotationBuilder, TypeBuilder { - final String _identifier; - final String _importFrom; - - ReferenceBuilder._(this._identifier, [this._importFrom]); +/// An abstract way of representing other types of [AstBuilder]. +class ReferenceBuilder implements AnnotationBuilder { + final String _name; - @override - void addGeneric(TypeBuilder type) { - throw new UnsupportedError('Use type() to be able to add generics'); - } + ReferenceBuilder._(this._name); @override - Annotation buildAnnotation([Scope scope = Scope.identity]) { + Annotation buildAnnotation([Scope scope]) { return new Annotation( - $at, - buildExpression(scope), - null, - null, - null, - ); - } - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildExpression(scope); - - @override - Expression buildExpression([Scope scope = Scope.identity]) { - return scope.getIdentifier(_identifier, _importFrom); + ); } @override - TypeName buildType([Scope scope = Scope.identity]) { - return generic().buildType(scope); - } - - /// Returns converted to a [TypeBuilder]. - TypeBuilder generic([Iterable generics = const []]) { - final type = new TypeBuilder(_identifier, importFrom: _importFrom); - generics.forEach(type.addGeneric); - return type; - } + AstNode buildAst([Scope scope]) => throw new UnimplementedError(); } diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart index 2340448..c8414cc 100644 --- a/lib/src/builders/shared.dart +++ b/lib/src/builders/shared.dart @@ -1,15 +1,54 @@ -import 'package:code_builder/code_builder.dart'; +// 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. -export 'shared/annotations_mixin.dart'; -export 'shared/parameters_mixin.dart'; -export 'shared/statements_mixin.dart'; +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; -/// Marker interface for valid members of [ConstructorBuilder]. -abstract class ValidConstructorMember implements AstBuilder { - ValidConstructorMember._doNotExtend(); +/// Lazily builds an analyzer [AstNode] when [buildAst] is invoked. +/// +/// Most builders should also have specific typed methods for returning their +/// type of AST node, such as `buildExpression` for returning an `Expression` +/// AST. +abstract class AstBuilder { + /// Returns an [AstNode] representing the state of the current builder. + /// + /// If [scope] is provided then identifiers are automatically prefixed and + /// imports are collected in order to emit a final File AST that does not + /// have conflicting or missing imports. + T buildAst([Scope scope]); } -/// Marker interface for valid members of a [ParameterBuilder]. -abstract class ValidParameterMember implements AstBuilder { - ValidParameterMember._doNotExtend(); +/// Maintains a scope to prevent conflicting or missing imports in a file. +abstract class Scope { + /// A [Scope] that does not apply any prefixing. + static const Scope identity = const _IdentityScope(); + + /// Returns an [Identifier] for [name]. + /// + /// Optionally, specify the [importFrom] URI. + Identifier getIdentifier(String name, [Uri importFrom]); +} + +class _IdentityScope implements Scope { + const _IdentityScope(); + + @override + Identifier getIdentifier(String name, [_]) => stringIdentifier(name); +} + +/// Returns a string [Literal] from [value]. +Identifier stringIdentifier(String value) => new SimpleIdentifier( + stringToken(value), +); + +/// Returns a string [Token] from [value]. +Token stringToken(String value) => new StringToken(TokenType.STRING, value, 0); + +/// Returns an [Identifier] for [name] via [scope]. +/// +/// If [scope] is `null`, automatically uses [Scope.identity]. +Identifier getIdentifier(Scope scope, String name, [Uri importFrom]) { + return (scope ?? Scope.identity).getIdentifier(name, importFrom); } diff --git a/lib/src/builders/shared/annotations_mixin.dart b/lib/src/builders/shared/annotations_mixin.dart deleted file mode 100644 index 886d8d4..0000000 --- a/lib/src/builders/shared/annotations_mixin.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; - -/// A builder that supports adding annotations as metadata. -abstract class HasAnnotations { - /// Lazily adds [annotation] as a builder. - /// - /// When the AST is built, [AnnotationBuilder.buildAnnotation] is invoked. - void addAnnotation(AnnotationBuilder annotation); - - /// Lazily adds [annotations] as builders. - /// - /// When the AST is built, [AnnotationBuilder.buildAnnotation] is invoked. - void addAnnotations(Iterable annotations); -} - -/// A mixin to add [addAnnotation] and [getAnnotations] to a builder. -abstract class HasAnnotationsMixin implements HasAnnotations { - final List _annotations = []; - - @override - void addAnnotation(AnnotationBuilder annotation) { - _annotations.add(annotation); - } - - @override - void addAnnotations(Iterable annotations) { - _annotations.addAll(annotations); - } - - /// Returns all annotations added by [addAnnotation]. - List getAnnotations() { - return new List.unmodifiable(_annotations); - } - - /// Returns a built list of [Annotation] ASTs. - List toAnnotationAsts([Scope scope = Scope.identity]) { - return _annotations - .map/**/((a) => a.buildAnnotation(scope)) - .toList(); - } -} diff --git a/lib/src/builders/shared/parameters_mixin.dart b/lib/src/builders/shared/parameters_mixin.dart deleted file mode 100644 index 22522c7..0000000 --- a/lib/src/builders/shared/parameters_mixin.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/src/tokens.dart'; - -/// A builder that supports adding annotations as metadata. -abstract class HasParameters { - /// Lazily adds [parameter] as a builder for a named parameter. - /// - /// When the AST is built, [ParameterBuilder] is also built. - void addNamedParameter(ParameterBuilder parameter); - - /// Lazily adds [parameters] as builders for named parameters. - /// - /// When the AST is built, [ParameterBuilder] is also built. - void addNamedParameters(Iterable parameters); - - /// Lazily adds [parameter] as a builder for a positional parameter. - /// - /// When the AST is built, [ParameterBuilder] is also built. - void addPositionalParameter(ParameterBuilder parameter); - - /// Lazily adds [parameters] as builders for positional parameters. - /// - /// When the AST is built, [ParameterBuilder] is also built. - void addPositionalParameters(Iterable parameters); -} - -/// Associates a [parameter] with the [kind] that should be emitted. -/// -/// Convenience class for users of [HasParametersMixin]. -class ParameterWithKind { - /// Either a named or positional parameter. - final ParameterKind kind; - - /// Parameter builder. - final ParameterBuilder parameter; - - ParameterWithKind._positional(this.parameter) - : kind = ParameterKind.POSITIONAL; - - ParameterWithKind._named(this.parameter) : kind = ParameterKind.NAMED; -} - -/// A mixin to add [addAnnotation] and [getAnnotations] to a builder. -abstract class HasParametersMixin implements HasParameters { - final List _parameters = []; - - @override - void addNamedParameter(ParameterBuilder parameter) { - _parameters.add(new ParameterWithKind._named(parameter)); - } - - @override - void addNamedParameters(Iterable parameters) { - parameters.forEach(addNamedParameter); - } - - @override - void addPositionalParameter(ParameterBuilder parameter) { - _parameters.add(new ParameterWithKind._positional(parameter)); - } - - @override - void addPositionalParameters(Iterable parameters) { - parameters.forEach(addPositionalParameter); - } - - /// Clones all added parameters to [mixin]. - void cloneParametersTo(HasParameters mixin) { - for (final parameter in _parameters) { - if (parameter.kind == ParameterKind.POSITIONAL) { - mixin.addPositionalParameter(parameter.parameter); - } else { - mixin.addNamedParameter(parameter.parameter); - } - } - } - - /// Returns a built list of [Annotation] ASTs. - FormalParameterList toFormalParameterList([Scope scope = Scope.identity]) { - return new FormalParameterList( - $openParen, - _parameters.map/**/((p) { - if (p.kind == ParameterKind.POSITIONAL) { - return p.parameter.buildPositional(scope); - } else { - return p.parameter.buildNamed(scope); - } - }).toList(), - null, - null, - $closeParen, - ); - } -} diff --git a/lib/src/builders/shared/statements_mixin.dart b/lib/src/builders/shared/statements_mixin.dart deleted file mode 100644 index 77d33a3..0000000 --- a/lib/src/builders/shared/statements_mixin.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; - -/// A builder that supports adding statements. -abstract class HasStatements { - /// Lazily adds [statement] as a builder. - /// - /// When the AST is built, [StatementBuilder.buildStatement] is invoked. - void addStatement(StatementBuilder statement); - - /// Lazily adds [statements] as builders. - /// - /// When the AST is built, [StatementBuilder.buildStatement] is invoked. - void addStatements(Iterable statements); -} - -/// A mixin to add [addStatement] and [addStatements] to a builder. -abstract class HasStatementsMixin implements HasStatements { - final List _statements = []; - - @override - void addStatement(StatementBuilder statement) { - _statements.add(statement); - } - - @override - void addStatements(Iterable statements) { - _statements.addAll(statements); - } - - /// Clones all added statements to [mixin]. - void cloneStatementsTo(HasStatements mixin) { - mixin.addStatements(_statements); - } - - /// Whether any statements have been added. - bool get hasStatements => _statements.isNotEmpty; - - /// Returns a built list of [Annotation] ASTs. - List toStatementAsts([Scope scope = Scope.identity]) { - return _statements - .map/**/((a) => a.buildStatement(scope)) - .toList(); - } -} diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart index 77ad7c8..df5dff4 100644 --- a/lib/src/builders/statement.dart +++ b/lib/src/builders/statement.dart @@ -1,12 +1,34 @@ import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart' show AstBuilder, Scope; import 'package:code_builder/src/builders/shared.dart'; -/// Builds an [Statement] AST. -abstract class StatementBuilder implements AstBuilder, ValidConstructorMember { - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildStatement(scope); +/// Lazily builds an [Statement] AST when [buildStatement] is invoked. +abstract class StatementBuilder implements AstBuilder { + /// Returns an [Statement] AST representing the builder. + Statement buildStatement([Scope scope]); +} + +/// An [AstBuilder] that can add [StatementBuilder]. +abstract class HasStatements implements AstBuilder { + final List _statements = []; + + /// Adds [statement] to the builder. + void addStatement(StatementBuilder statement) { + _statements.add(statement); + } + + /// Adds [statements] to the builder. + void addStatements(Iterable statements) { + _statements.addAll(statements); + } +} + +/// Implements [HasStatements]. +abstract class HasStatementsMixin extends HasStatements { + /// Clones all expressions to [clone]. + void cloneStatementsTo(HasStatements clone) { + clone.addStatements(_statements); + } - /// Returns a Dart [Statement] AST reprsenting the current builder state. - Statement buildStatement([Scope scope = Scope.identity]); + /// Returns a [List] of all built [Statement]s. + List buildStatements([Scope scope]) => _statements.map/**/((e) => e.buildStatement(scope)).toList(); } diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart deleted file mode 100644 index fe89965..0000000 --- a/lib/src/builders/type.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/src/builders/shared.dart'; -import 'package:code_builder/src/tokens.dart'; - -/// Returns a new [TypeBuilder]. -/// -/// Shorthand for `new TypeBuilder` for more functional aficionados. -TypeBuilder type(String identifier, [String importFrom]) { - return new TypeBuilder(identifier, importFrom: importFrom); -} - -/// Builds an [TypeName] AST. -class TypeBuilder implements AstBuilder, ValidParameterMember { - final List _generics = []; - final String _identifier; - final String _importFrom; - - /// Create a new [TypeBuilder] from an identifier. - /// - /// Optionally specify where the reference should be [importFrom]. - TypeBuilder( - this._identifier, { - String importFrom, - }) - : _importFrom = importFrom; - - /// Adds a generic [type] parameter. - void addGeneric(TypeBuilder type) { - _generics.add(type); - } - - @override - AstNode buildAst([Scope scope = Scope.identity]) => buildType(scope); - - /// Returns a new [TypeName] AST. - TypeName buildType([Scope scope = Scope.identity]) { - return new TypeName( - scope.getIdentifier(_identifier, _importFrom), - _generics.isEmpty - ? null - : new TypeArgumentList( - $lt, - _generics.map/**/((g) => g.buildType(scope)).toList(), - $gt, - ), - ); - } -} diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart deleted file mode 100644 index 281b6b7..0000000 --- a/lib/src/pretty_printer.dart +++ /dev/null @@ -1,106 +0,0 @@ -// 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; - -/// Augments [AstNode.toSource] by adding some whitespace/line breaks. -/// -/// The final result is run through `dartfmt`. -/// -/// This is the _recommended_ output (but not required) when comparing ASTs -/// to expected golden files/text blobs. -String prettyToSource(AstNode astNode) { - var buffer = new PrintBuffer(); - var visitor = new _PrettyToSourceVisitor(buffer); - astNode.accept(visitor); - var source = buffer.toString(); - try { - return dartfmt(source); - } on FormatterException catch (_) { - return source; - } -} - -// TODO(matanl): Remove copied-pasted methods when API becomes available. -// https://github.com/dart-lang/sdk/issues/27169 -class _PrettyToSourceVisitor extends ToSourceVisitor { - // https://github.com/dart-lang/sdk/issues/27301 - final StringBuffer _buffer; - - _PrettyToSourceVisitor(PrintBuffer buffer) - : _buffer = buffer, - super(buffer); - - @override - Object visitClassDeclaration(ClassDeclaration node) { - _visitNodeListWithSeparatorAndSuffix(node.metadata, " ", " "); - _visitTokenWithSuffix(node.abstractKeyword, " "); - _buffer.write("class "); - _visitNode(node.name); - _visitNode(node.typeParameters); - _visitNodeWithPrefix(" ", node.extendsClause); - _visitNodeWithPrefix(" ", node.withClause); - _visitNodeWithPrefix(" ", node.implementsClause); - _buffer.write(" {"); - _visitNodeListWithSeparator(node.members, "\n\n"); - _buffer.write("}"); - return null; - } - - // Safely visit the given [node]. - void _visitNode(AstNode node) { - if (node != null) { - node.accept(this); - } - } - - // Write a list of [nodes], separated by the given [separator]. - void _visitNodeListWithSeparator(NodeList nodes, String separator) { - if (nodes != null) { - int size = nodes.length; - for (int i = 0; i < size; i++) { - if (i > 0) { - _buffer.write(separator); - } - nodes[i].accept(this); - } - } - } - - // 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) { - if (nodes != null) { - int size = nodes.length; - if (size > 0) { - for (int i = 0; i < size; i++) { - if (i > 0) { - _buffer.write(separator); - } - nodes[i].accept(this); - } - _buffer.write(suffix); - } - } - } - - // Safely visit the given [node], writing the [prefix] before the node if it - // is non-`null`. - void _visitNodeWithPrefix(String prefix, AstNode node) { - if (node != null) { - _buffer.write(prefix); - node.accept(this); - } - } - - // Safely visit the given [token], writing the [suffix] after the token if it - // is non-`null`. - void _visitTokenWithSuffix(Token token, String suffix) { - if (token != null) { - _buffer.write(token.lexeme); - _buffer.write(suffix); - } - } -} diff --git a/lib/src/scope.dart b/lib/src/scope.dart deleted file mode 100644 index b453ccb..0000000 --- a/lib/src/scope.dart +++ /dev/null @@ -1,83 +0,0 @@ -// 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; - -/// Determines an [Identifier] deppending on where it appears. -/// -/// __Example use__: -/// void useContext(Scope scope) { -/// // Prints Identifier "i1.Foo" -/// print(scope.getIdentifier('Foo', 'package:foo/foo.dart'); -/// -/// // Prints Identifier "i1.Bar" -/// print(scope.getIdentifier('Bar', 'package:foo/foo.dart'); -/// -/// // Prints Identifier "i2.Baz" -/// print(scope.getIdentifier('Baz', 'package:bar/bar.dart'); -/// } -abstract class Scope { - /// A scoping context that does nothing. - static const identity = const _IdentityScope(); - - /// Create a default scope context. - /// - /// Actual implementation is _not_ guaranteed, only that all import prefixes - /// will be unique in a given scope (actual implementation may be naive). - factory Scope() = _IncrementingScope; - - /// Create a context that just de-duplicates imports (no scoping). - factory Scope.dedupe() = _DeduplicatingScope; - - /// Given a [symbol] and its known [importUri], return an [Identifier]. - Identifier getIdentifier(String symbol, String importUri); - - /// Returns a list of all imports needed to resolve identifiers. - Iterable< /*ImportBuilder*/ dynamic> getImports(); -} - -class _DeduplicatingScope implements Scope { - final Set _imports = new Set(); - - @override - Identifier getIdentifier(String symbol, String import) { - _imports.add(import); - return _stringIdentifier(symbol); - } - - @override - Iterable< /*ImportBuilder*/ dynamic> getImports() { - return _imports - .map/* new /*ImportBuilder*/ dynamic(i)); - } -} - -class _IdentityScope implements Scope { - const _IdentityScope(); - - @override - Identifier getIdentifier(String symbol, _) => _stringIdentifier(symbol); - - @override - Iterable< /*ImportBuilder*/ dynamic> getImports() => const []; -} - -class _IncrementingScope implements Scope { - final Map _imports = {}; - - int _counter = 0; - - @override - Identifier getIdentifier(String symbol, String import) { - var newId = _imports.putIfAbsent(import, () => ++_counter); - return new PrefixedIdentifier( - _stringIdentifier('_i$newId'), $period, _stringIdentifier(symbol)); - } - - @override - Iterable< /*ImportBuilder*/ dynamic> getImports() { - return _imports.keys.map/* new /*ImportBuilder*/ dynamic(i, prefix: '_i${_imports[i]}')); - } -} diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index cacd355..e69de29 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -1,125 +0,0 @@ -// 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. - -import 'package:analyzer/dart/ast/token.dart'; -import 'package:analyzer/src/dart/ast/token.dart'; - -/// The `abstract` token. -final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); - -/// The `assert` token. -final Token $assert = new KeywordToken(Keyword.ASSERT, 0); - -/// The `class` token. -final Token $class = new KeywordToken(Keyword.CLASS, 0); - -/// The `extends` token. -final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); - -/// The `implements` token. -final Token $implements = new KeywordToken(Keyword.IMPLEMENTS, 0); - -/// The `with` token. -final Token $with = new KeywordToken(Keyword.WITH, 0); - -/// The `static` token. -final Token $static = new KeywordToken(Keyword.STATIC, 0); - -/// The `final` token. -final Token $final = new KeywordToken(Keyword.FINAL, 0); - -/// The `const` token. -final Token $const = new KeywordToken(Keyword.CONST, 0); - -/// The `var` token. -final Token $var = new KeywordToken(Keyword.VAR, 0); - -/// The `this` token. -final Token $this = new KeywordToken(Keyword.THIS, 0); - -/// The `library` token. -final Token $library = new KeywordToken(Keyword.LIBRARY, 0); - -/// The `part` token. -final Token $part = new KeywordToken(Keyword.PART, 0); - -/// The `of` token. -final Token $of = new StringToken(TokenType.KEYWORD, 'of', 0); - -/// The `true` token. -final Token $true = new KeywordToken(Keyword.TRUE, 0); - -/// The `false` token. -final Token $false = new KeywordToken(Keyword.FALSE, 0); - -/// The `null` token. -final Token $null = new KeywordToken(Keyword.NULL, 0); - -/// The `new` token. -final Token $new = new KeywordToken(Keyword.NEW, 0); - -// Simple tokens - -/// The `@` token. -final Token $at = new Token(TokenType.AT, 0); - -/// The '(' token. -final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); - -/// The ')' token. -final Token $closeParen = new Token(TokenType.CLOSE_PAREN, 0); - -/// The '{' token. -final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); - -/// The '}' token. -final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); - -/// The `>` token. -final Token $gt = new Token(TokenType.GT, 0); - -/// The `<` token. -final Token $lt = new Token(TokenType.LT, 0); - -/// The ':' token. -final Token $colon = new Token(TokenType.COLON, 0); - -/// The `else` token. -final Token $else = new KeywordToken(Keyword.ELSE, 0); - -/// The ';' token. -final Token $semicolon = new Token(TokenType.SEMICOLON, 0); - -/// The '=' token. -final Token $equals = new Token(TokenType.EQ, 0); - -/// The `==` token. -final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); - -/// The `if` token. -final Token $if = new KeywordToken(Keyword.IF, 0); - -/// The '??=' token. -final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); - -/// The `!` token. -final Token $not = new Token(TokenType.BANG, 0); - -/// The `!=` token. -final Token $notEquals = new Token(TokenType.BANG_EQ, 0); - -/// The '.' token. -final Token $period = new Token(TokenType.PERIOD, 0); - -/// The '+' token. -final Token $plus = new Token(TokenType.PLUS, 0); - -/// The `return` token. -final Token $return = new KeywordToken(Keyword.RETURN, 0); - -/// Returns a string token for the given string [s]. -StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); - -/// Returns an int token for the given int [value]. -StringToken intToken(int value) => new StringToken(TokenType.INT, '$value', 0); diff --git a/lib/testing.dart b/lib/testing.dart deleted file mode 100644 index ad4d623..0000000 --- a/lib/testing.dart +++ /dev/null @@ -1 +0,0 @@ -export 'testing/equals_source.dart'; diff --git a/lib/testing/equals_source.dart b/lib/testing/equals_source.dart deleted file mode 100644 index c5cfa7c..0000000 --- a/lib/testing/equals_source.dart +++ /dev/null @@ -1,122 +0,0 @@ -// 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. - -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/src/tokens.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:matcher/matcher.dart'; - -/// Returns identifiers that are just the file name. -/// -/// For example `getIdentifier('Foo', 'package:foo/foo.dart')` would return -/// the identifier "foo.Foo". This isn't safe enough for use in a actual -/// code-gen but makes it easy to debug issues in our tests. -const Scope simpleNameScope = const _SimpleNameScope(); - -/// Returns a [Matcher] that checks a [CodeBuilder] versus [source]. -/// -/// On failure, uses the default string matcher to show a detailed diff between -/// the expected and actual source code results. -/// -/// **NOTE**: Runs `dartfmt` _and_ prints the source with additional formatting -/// over the default `Ast.toSource` implementation (i.e. adds new lines between -/// methods in classes, and more). -Matcher equalsSource( - String source, { - Scope scope: Scope.identity, -}) { - try { - source = dartfmt(source); - } on FormatterException catch (_) {} - return new _EqualsSource( - scope, - source, - ); -} - -/// Returns a [Matcher] that checks a [CodeBuilder versus [source]. -/// -/// On failure, uses the default string matcher to show a detailed diff between -/// the expected and actual source code results. -/// -/// **NOTE**: Whitespace is ignored. -Matcher equalsUnformatted( - String source, { - Scope scope: Scope.identity, -}) { - return new _EqualsSource( - scope, - source, - ); -} - -Identifier _stringId(String s) => new SimpleIdentifier(stringToken(s)); - -class _EqualsSource extends Matcher { - final Scope _scope; - final String _source; - - _EqualsSource(this._scope, this._source); - - @override - Description describe(Description description) { - return equals(_source).describe(description); - } - - @override - Description describeMismatch( - item, - Description mismatchDescription, - Map matchState, - bool verbose, - ) { - if (item is AstBuilder) { - var origin = _formatAst(item); - return equalsIgnoringWhitespace(_source).describeMismatch( - origin, - mismatchDescription.addDescriptionOf(origin), - matchState, - verbose, - ); - } else { - return mismatchDescription.add('$item is not a CodeBuilder'); - } - } - - @override - bool matches(item, _) { - if (item is AstBuilder) { - return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); - } - return false; - } - - String _formatAst(AstBuilder builder) { - var astNode = builder.buildAst(_scope); - return prettyToSource(astNode); - } -} - -// Delegates to the default matcher (which delegates further). -// -// Would be nice to just use more of package:matcher directly: -// https://github.com/dart-lang/matcher/issues/36 -class _SimpleNameScope implements Scope { - const _SimpleNameScope(); - - @override - Identifier getIdentifier(String symbol, String importUri) { - var fileWithoutExt = - Uri.parse(importUri).pathSegments.last.split('.').first; - return new PrefixedIdentifier( - _stringId(fileWithoutExt), - $period, - _stringId(symbol), - ); - } - - @override - Iterable< /*ImportBuilder*/ dynamic> getImports() => const []; -} diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart new file mode 100644 index 0000000..75ef29b --- /dev/null +++ b/test/builders/class_test.dart @@ -0,0 +1,6 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:test/test.dart'; + +void main() { + +} diff --git a/test/builders/shared_test.dart b/test/builders/shared_test.dart new file mode 100644 index 0000000..05232d5 --- /dev/null +++ b/test/builders/shared_test.dart @@ -0,0 +1,16 @@ +import 'package:code_builder/src/builders/shared.dart'; +import 'package:test/test.dart'; + +void main() { + test('stringIdentifier should return a string identifier', () { + expect(stringIdentifier('Coffee').toSource(), 'Coffee'); + }); + + test('stringToken should return a string token', () { + expect(stringToken('Coffee').value().toString(), 'Coffee'); + }); + + test('Scope.identity should return an unprefixed identifier', () { + expect(Scope.identity.getIdentifier('Coffee').toSource(), 'Coffee'); + }); +} diff --git a/test/class_test.dart b/test/class_test.dart deleted file mode 100644 index e684f9f..0000000 --- a/test/class_test.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - group('$ClassBuilder', () { - test('should emit a simple class', () { - expect(clazz('Animal'), equalsSource(r''' - class Animal {} - ''')); - }); - - test('should emit an annotated class', () { - expect( - clazz('Animal', [ - annotation('deprecated'), - ]), - equalsSource(r''' - @deprecated - class Animal {} - '''), - ); - }); - }); - - group('$ConstructorBuilder', () { - ClassBuilder animal; - - setUp(() => animal = clazz('Animal')); - - test('should emit a simple default constructor', () { - expect( - constructor().attachTo(animal), - equalsSource(r''' - Animal(); - '''), - ); - }); - - test('should emit a named constructor', () { - expect( - constructorNamed('empty').attachTo(animal), - equalsSource(r''' - Animal.empty(); - '''), - ); - }); - - test('should emit a constructor with a statement body', () { - expect( - constructor([ - printMethod.call([literal('Hello World')]), - ]).attachTo(animal), - equalsSource(r''' - Animal() { - print('Hello World'); - } - '''), - ); - }); - - test('should emit a constructor with constructor initializers', () { - expect( - constructor([ - initializer('age', literal(0)), - initializer('delicious', literal(true)), - ]).attachTo(animal), - equalsSource(r''' - Animal() - : this.age = 0, - this.delicious = true; - '''), - ); - }); - - test('should emit a constructor with a parameter', () { - expect( - constructor([ - parameter('foo'), - ]).attachTo(animal), - equalsSource(r''' - Animal(foo); - '''), - ); - }); - - test('should emit a constructor with an optional parameter', () { - expect( - constructor([ - parameter('foo').toDefault(), - ]).attachTo(animal), - equalsSource(r''' - Animal([foo]); - '''), - ); - }); - - test('should emit a constructor with a field formal parameter', () { - expect( - constructor([ - parameter('foo').toField(), - ]).attachTo(animal), - equalsSource(r''' - Animal(this.foo); - '''), - ); - }); - - test('should emit a constructor with a named parameter', () { - expect( - new ConstructorBuilder(animal)..addNamedParameter(parameter('foo')), - equalsSource(r''' - Animal({foo}); - '''), - ); - }); - - test('should emit a constructor with a named parameter with a value', () { - expect( - new ConstructorBuilder(animal) - ..addNamedParameter(parameter('foo', [literal(true)])), - equalsSource(r''' - Animal({foo: true}); - '''), - ); - }); - }); -} diff --git a/test/expression_test.dart b/test/expression_test.dart deleted file mode 100644 index 18048a4..0000000 --- a/test/expression_test.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/src/builders/expression.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('Literals', () { - test('literal(null) should emit null', () { - expect(literal(null), equalsSource('null')); - }); - - test('literal(true) should emit true', () { - expect(literal(true), equalsSource('true')); - }); - - test('literal(false) should emit false', () { - expect(literal(false), equalsSource('false')); - }); - - test('literal() should emit a string', () { - expect(literal('Hello'), equalsSource("'Hello'")); - }); - - test('literal() should throw', () { - expect(() => literal(new Object()), throwsArgumentError); - }); - }); - - group('$ExpressionBuilder', () { - test('asAssert() should emit an assert statement', () { - expect(literal(true).asAssert(), equalsSource(r''' - assert(true); - ''')); - }); - - test('assign should emit a setter', () { - expect(literal(true).assign('fancy'), equalsSource(r''' - fancy = true - ''')); - }); - - test('asConst should emit assignment to a new const variable', () { - expect(literal(true).asConst('fancy'), equalsSource(r''' - const fancy = true; - ''')); - }); - - test('asConst should emit assignment to a new typed const variable', () { - expect(literal(true).asConst('fancy', typeBool), equalsSource(r''' - const bool fancy = true; - ''')); - }); - - test('asFinal should emit assignment to a new final variable', () { - expect(literal(true).asFinal('fancy'), equalsSource(r''' - final fancy = true; - ''')); - }); - - test('asFinal should emit assignment to a new typed final variable', () { - expect(literal(true).asFinal('fancy', typeBool), equalsSource(r''' - final bool fancy = true; - ''')); - }); - - test('asVar should emit assignment to a new var', () { - expect(literal(true).asVar('fancy'), equalsSource(r''' - var fancy = true; - ''')); - }); - - group('asIf', () { - test('should emit a single if-block', () { - expect(literal(true).asIf(), equalsSource(r''' - if (true) {} - ''')); - }); - - test('should emit an if-else block', () { - expect(literal(true).asIf().andElse(literal(false)), equalsSource(r''' - if (true) { - - } - else if (false) { - - } - ''')); - }); - - test('should emit an if-else-if-else-else block', () { - expect( - literal(1) - .equals(literal(2)) - .asIf() - .andElse(literal(2).equals(literal(3))) - .andElse(literal(3).equals(literal(4))) - .andElse(), - equalsSource(r''' - if (1 == 2) { - - } else if (2 == 3) { - - } else if (3 == 4) { - - } else { - - } - ''')); - }); - }); - - test('asReturn should emit a return statement', () { - expect(literal(true).asReturn(), equalsSource(r''' - return true; - ''')); - }); - - test('assign should emit an asssignment expression', () { - expect(literal(true).assign('fancy'), equalsSource(r''' - fancy = true - ''')); - }); - - test('asStatement should emit an expression as a statement', () { - expect(literal(true).asStatement(), equalsSource(r''' - true; - ''')); - }); - - test('equals should compare two expressions', () { - expect(literal(true).equals(literal(true)), equalsSource(r''' - true == true - ''')); - }); - - test('not equals should compare two expressions', () { - expect(literal(true).notEquals(literal(true)), equalsSource(r''' - true != true - ''')); - }); - - test('not should negate an expression', () { - expect(literal(true).not(), equalsSource(r''' - !true - ''')); - }); - - test('parentheses should wrap an expression', () { - expect(literal(true).parentheses(), equalsSource(r''' - (true) - ''')); - }); - - test('+ should emit the sum two expressions', () { - expect(literal(1) + literal(2), equalsSource(r''' - 1 + 2 - ''')); - }); - }); -} diff --git a/test/parameter_test.dart b/test/parameter_test.dart deleted file mode 100644 index 50630b5..0000000 --- a/test/parameter_test.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/testing.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit a simple untyped parameter', () { - expect( - parameter('foo'), - equalsSource('foo'), - ); - }); - - test('should emit a simple typed parameter', () { - expect( - parameter('foo', [type('String')]), - equalsSource('String foo'), - ); - }); - - test('should emit a field-formal parameter', () { - expect( - parameter('foo').toField(), - equalsSource('this.foo'), - ); - }); - - test('should emit a parameter with a default value', () { - expect( - parameter('foo', [literal(true)]), - equalsSource('foo = true'), - ); - }); - - test('shuld emit a field-formal parameter with a default value', () { - expect( - parameter('foo', [literal(true)]).toField(), - equalsSource('this.foo = true'), - ); - }); -} diff --git a/test/reference_test.dart b/test/reference_test.dart deleted file mode 100644 index b68bc3f..0000000 --- a/test/reference_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - group('reference', () { - test('should emit an identifier', () { - expect(reference('fooBar'), equalsSource(r'fooBar')); - }); - - test('should emit an expression when used as one', () { - expect(reference('a').equals(reference('b')), equalsSource(r''' - a == b - ''')); - }); - - test('should emit a scoped identifier', () { - expect( - reference('fooBar', 'package:foo_bar/foo_bar.dart'), - equalsSource( - r''' - foo_bar.fooBar - ''', - scope: simpleNameScope), - ); - }); - - test('should emit a method invocation on the reference', () { - expect( - reference('print').call(), - equalsSource(r''' - print() - '''), - ); - }); - - test('should emit a method invocation on a method of the refernece', () { - expect( - reference('foo').invoke('bar'), - equalsSource(r''' - foo.bar() - '''), - ); - }); - - test('should emit a method invocation with named args', () { - expect( - reference('foo').callWith(named: {'bar': literal(true)}), - equalsSource(r''' - foo(bar: true) - '''), - ); - }); - - test('should emit a method invocation on a method with named args', () { - expect( - reference('foo').invokeWith('bar', named: {'baz': literal(true)}), - equalsSource(r''' - foo.bar(baz: true) - '''), - ); - }); - }); -} diff --git a/test/scope_test.dart b/test/scope_test.dart deleted file mode 100644 index 4851c3f..0000000 --- a/test/scope_test.dart +++ /dev/null @@ -1,96 +0,0 @@ -// 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. - -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:test/test.dart'; - -void main() { - group('Identity scope', () { - Scope scope; - - setUp(() => scope = Scope.identity); - - test('should do nothing', () { - var identifiers = [ - scope.getIdentifier('Foo', 'package:foo/foo.dart'), - scope.getIdentifier('Bar', 'package:foo/foo.dart'), - scope.getIdentifier('Baz', 'package:baz/baz.dart'), - ].map/**/((i) => i.toSource()); - - expect( - identifiers, - [ - 'Foo', - 'Bar', - 'Baz', - ], - ); - - expect(scope.getImports(), isEmpty); - }); - }); - - group('Deduplicating scope', () { - Scope scope; - - setUp(() => scope = new Scope.dedupe()); - - test('should just output non-prefixed and de-duplicate imports', () { - var identifiers = [ - scope.getIdentifier('Foo', 'package:foo/foo.dart'), - scope.getIdentifier('Bar', 'package:foo/foo.dart'), - scope.getIdentifier('Baz', 'package:baz/baz.dart'), - ].map/**/((i) => i.toSource()); - - expect( - identifiers, - [ - 'Foo', - 'Bar', - 'Baz', - ], - ); - - expect( - scope.getImports().map/**/((i) => i.toAst().toSource()), - [ - r"import 'package:foo/foo.dart';", - r"import 'package:baz/baz.dart';", - ], - ); - }); - }, skip: ''); - - group('Incrementing scope', () { - Scope scope; - - setUp(() => scope = new Scope()); - - test('should out prefixed with a counter', () { - var identifiers = [ - scope.getIdentifier('Foo', 'package:foo/foo.dart'), - scope.getIdentifier('Bar', 'package:foo/foo.dart'), - scope.getIdentifier('Baz', 'package:baz/baz.dart'), - ].map/**/((i) => i.toSource()); - - expect( - identifiers, - [ - '_i1.Foo', - '_i1.Bar', - '_i2.Baz', - ], - ); - - expect( - scope.getImports().map/**/((i) => i.toAst().toSource()), - [ - r"import 'package:foo/foo.dart' as _i1;", - r"import 'package:baz/baz.dart' as _i2;", - ], - ); - }); - }, skip: ''); -} diff --git a/test/type_test.dart b/test/type_test.dart deleted file mode 100644 index c8bbf1d..0000000 --- a/test/type_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit a type', () { - expect(reference('List'), equalsSource(r'List')); - }); - - test('should emit a type with a generic type parameter', () { - expect( - reference('List').generic([reference('String')]), - equalsSource(r''' - List - '''), - ); - }); - - test('should emit a type with prefixed generic types', () { - expect( - reference('SuperList', 'package:super/list.dart').generic([ - reference('Super', 'package:super/super.dart'), - ]), - equalsSource( - r''' - list.SuperList - ''', - scope: simpleNameScope), - ); - }); -} diff --git a/tool/travis.sh b/tool/travis.sh deleted file mode 100755 index cab493d..0000000 --- a/tool/travis.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# 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. - -# Fast fail the script on failures. -set -e - -# Verify that the libraries are error free. -dartanalyzer --fatal-warnings \ - lib/code_builder.dart \ - lib/dart/core.dart \ - lib/testing/equals_source.dart - -# Run the tests. -pub run test - -# Install dart_coveralls; gather and send coverage data. -if [ "$COVERALLS_TOKEN" ] && [ "$TRAVIS_DART_VERSION" = "stable" ]; then - dart tool/create_test_all.dart - pub global activate dart_coveralls - pub global run dart_coveralls report \ - --retry 2 \ - --exclude-test-files \ - tool/test_all.dart - rm tool/test_all.dart -fi From 9a9b3190949f88c4a25dac7ba9e1b23e6ef12eff Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 13:00:20 -0700 Subject: [PATCH 04/19] . --- lib/code_builder.dart | 5 ++ lib/src/builders/annotation.dart | 4 + lib/src/builders/class.dart | 4 + lib/src/builders/expression.dart | 4 + lib/src/builders/reference.dart | 11 ++- lib/src/builders/shared.dart | 6 +- lib/src/builders/statement.dart | 4 + lib/src/tokens.dart | 125 +++++++++++++++++++++++++++++++ lib/testing.dart | 0 test/builders/class_test.dart | 2 + test/builders/shared_test.dart | 1 + 11 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 lib/testing.dart diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 7c8f4b2..a739507 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -1,4 +1,9 @@ +// 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. + export 'src/builders/annotation.dart' show AnnotationBuilder; export 'src/builders/class.dart' show clazz, ClassBuilder; export 'src/builders/expression.dart' show ExpressionBuilder; +export 'src/builders/reference.dart' show reference, ReferenceBuilder; export 'src/builders/statement.dart' show StatementBuilder; diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index 7dca1d1..34fd59f 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -1,3 +1,7 @@ +// 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. + import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/class.dart'; import 'package:code_builder/src/builders/shared.dart'; diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index 7a0945d..7b1ba11 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -1,3 +1,7 @@ +// 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. + import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/shared.dart'; diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index f1eee7e..c704728 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -1,3 +1,7 @@ +// 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. + import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/shared.dart'; diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index b1effde..36f5f8f 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -1,6 +1,11 @@ +// 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. + import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; /// Creates a reference called [name]. ReferenceBuilder reference(String name, [String importUri]) { @@ -16,7 +21,11 @@ class ReferenceBuilder implements AnnotationBuilder { @override Annotation buildAnnotation([Scope scope]) { return new Annotation( - + $at, + stringIdentifier(_name), + null, + null, + null, ); } diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart index c8414cc..0e29867 100644 --- a/lib/src/builders/shared.dart +++ b/lib/src/builders/shared.dart @@ -3,8 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/src/tokens.dart'; /// Lazily builds an analyzer [AstNode] when [buildAst] is invoked. /// @@ -43,9 +42,6 @@ Identifier stringIdentifier(String value) => new SimpleIdentifier( stringToken(value), ); -/// Returns a string [Token] from [value]. -Token stringToken(String value) => new StringToken(TokenType.STRING, value, 0); - /// Returns an [Identifier] for [name] via [scope]. /// /// If [scope] is `null`, automatically uses [Scope.identity]. diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart index df5dff4..35625a3 100644 --- a/lib/src/builders/statement.dart +++ b/lib/src/builders/statement.dart @@ -1,3 +1,7 @@ +// 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. + import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/shared.dart'; diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index e69de29..cacd355 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -0,0 +1,125 @@ +// 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. + +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; + +/// The `abstract` token. +final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); + +/// The `assert` token. +final Token $assert = new KeywordToken(Keyword.ASSERT, 0); + +/// The `class` token. +final Token $class = new KeywordToken(Keyword.CLASS, 0); + +/// The `extends` token. +final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); + +/// The `implements` token. +final Token $implements = new KeywordToken(Keyword.IMPLEMENTS, 0); + +/// The `with` token. +final Token $with = new KeywordToken(Keyword.WITH, 0); + +/// The `static` token. +final Token $static = new KeywordToken(Keyword.STATIC, 0); + +/// The `final` token. +final Token $final = new KeywordToken(Keyword.FINAL, 0); + +/// The `const` token. +final Token $const = new KeywordToken(Keyword.CONST, 0); + +/// The `var` token. +final Token $var = new KeywordToken(Keyword.VAR, 0); + +/// The `this` token. +final Token $this = new KeywordToken(Keyword.THIS, 0); + +/// The `library` token. +final Token $library = new KeywordToken(Keyword.LIBRARY, 0); + +/// The `part` token. +final Token $part = new KeywordToken(Keyword.PART, 0); + +/// The `of` token. +final Token $of = new StringToken(TokenType.KEYWORD, 'of', 0); + +/// The `true` token. +final Token $true = new KeywordToken(Keyword.TRUE, 0); + +/// The `false` token. +final Token $false = new KeywordToken(Keyword.FALSE, 0); + +/// The `null` token. +final Token $null = new KeywordToken(Keyword.NULL, 0); + +/// The `new` token. +final Token $new = new KeywordToken(Keyword.NEW, 0); + +// Simple tokens + +/// The `@` token. +final Token $at = new Token(TokenType.AT, 0); + +/// The '(' token. +final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); + +/// The ')' token. +final Token $closeParen = new Token(TokenType.CLOSE_PAREN, 0); + +/// The '{' token. +final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); + +/// The '}' token. +final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); + +/// The `>` token. +final Token $gt = new Token(TokenType.GT, 0); + +/// The `<` token. +final Token $lt = new Token(TokenType.LT, 0); + +/// The ':' token. +final Token $colon = new Token(TokenType.COLON, 0); + +/// The `else` token. +final Token $else = new KeywordToken(Keyword.ELSE, 0); + +/// The ';' token. +final Token $semicolon = new Token(TokenType.SEMICOLON, 0); + +/// The '=' token. +final Token $equals = new Token(TokenType.EQ, 0); + +/// The `==` token. +final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); + +/// The `if` token. +final Token $if = new KeywordToken(Keyword.IF, 0); + +/// The '??=' token. +final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); + +/// The `!` token. +final Token $not = new Token(TokenType.BANG, 0); + +/// The `!=` token. +final Token $notEquals = new Token(TokenType.BANG_EQ, 0); + +/// The '.' token. +final Token $period = new Token(TokenType.PERIOD, 0); + +/// The '+' token. +final Token $plus = new Token(TokenType.PLUS, 0); + +/// The `return` token. +final Token $return = new KeywordToken(Keyword.RETURN, 0); + +/// Returns a string token for the given string [s]. +StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); + +/// Returns an int token for the given int [value]. +StringToken intToken(int value) => new StringToken(TokenType.INT, '$value', 0); diff --git a/lib/testing.dart b/lib/testing.dart new file mode 100644 index 0000000..e69de29 diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart index 75ef29b..f776972 100644 --- a/test/builders/class_test.dart +++ b/test/builders/class_test.dart @@ -2,5 +2,7 @@ import 'package:code_builder/code_builder.dart'; import 'package:test/test.dart'; void main() { + test('should emit an empty class', () { + }); } diff --git a/test/builders/shared_test.dart b/test/builders/shared_test.dart index 05232d5..1cf6215 100644 --- a/test/builders/shared_test.dart +++ b/test/builders/shared_test.dart @@ -1,4 +1,5 @@ import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; import 'package:test/test.dart'; void main() { From 860ee9510798f48b9e2dcf8818557888fa7273d1 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 13:02:15 -0700 Subject: [PATCH 05/19] . --- lib/code_builder.dart | 1 + lib/testing.dart | 91 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/lib/code_builder.dart b/lib/code_builder.dart index a739507..6030990 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -6,4 +6,5 @@ export 'src/builders/annotation.dart' show AnnotationBuilder; export 'src/builders/class.dart' show clazz, ClassBuilder; export 'src/builders/expression.dart' show ExpressionBuilder; export 'src/builders/reference.dart' show reference, ReferenceBuilder; +export 'src/builders/shared.dart' show AstBuilder, Scope; export 'src/builders/statement.dart' show StatementBuilder; diff --git a/lib/testing.dart b/lib/testing.dart index e69de29..3b0159d 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -0,0 +1,91 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/tokens.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:matcher/matcher.dart'; + +/// Returns a [Matcher] that checks an [AstBuilder] versus [source]. +/// +/// On failure, uses the default string matcher to show a detailed diff between +/// the expected and actual source code results. +/// +/// **NOTE**: Runs `dartfmt` _and_ prints the source with additional formatting +/// over the default `Ast.toSource` implementation (i.e. adds new lines between +/// methods in classes, and more). +Matcher equalsSource( + String source, { + Scope scope: Scope.identity, + }) { + try { + source = dartfmt(source); + } on FormatterException catch (_) {} + return new _EqualsSource( + scope, + source, + ); +} + +/// Returns a [Matcher] that checks a [CodeBuilder versus [source]. +/// +/// On failure, uses the default string matcher to show a detailed diff between +/// the expected and actual source code results. +/// +/// **NOTE**: Whitespace is ignored. +Matcher equalsUnformatted( + String source, { + Scope scope: Scope.identity, + }) { + return new _EqualsSource( + scope, + source, + ); +} + +class _EqualsSource extends Matcher { + final Scope _scope; + final String _source; + + _EqualsSource(this._scope, this._source); + + @override + Description describe(Description description) { + return equals(_source).describe(description); + } + + @override + Description describeMismatch( + item, + Description mismatchDescription, + Map matchState, + bool verbose, + ) { + if (item is AstBuilder) { + var origin = _formatAst(item); + return equalsIgnoringWhitespace(_source).describeMismatch( + origin, + mismatchDescription.addDescriptionOf(origin), + matchState, + verbose, + ); + } else { + return mismatchDescription.add('$item is not a CodeBuilder'); + } + } + + @override + bool matches(item, _) { + if (item is AstBuilder) { + return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); + } + return false; + } + + String _formatAst(AstBuilder builder) { + var astNode = builder.buildAst(_scope); + return prettyToSource(astNode); + } +} From 627042f58ad6b13941dcb5449dce60d02bc0397d Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 14:00:33 -0700 Subject: [PATCH 06/19] . --- lib/code_builder.dart | 17 +++++++- lib/src/builders/annotation.dart | 6 ++- lib/src/builders/expression.dart | 66 +++++++++++++++++++++++++++++- lib/src/builders/shared.dart | 4 +- lib/src/builders/statement.dart | 5 ++- lib/src/tokens.dart | 12 ++++-- lib/testing.dart | 48 +++++++++++----------- test/builders/class_test.dart | 20 +++++++++ test/builders/expression_test.dart | 43 +++++++++++++++++++ test/builders/reference_test.dart | 9 ++++ test/builders/shared_test.dart | 4 ++ 11 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 test/builders/expression_test.dart create mode 100644 test/builders/reference_test.dart diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 6030990..2a320db 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -2,9 +2,24 @@ // 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. +import 'package:dart_style/dart_style.dart'; +import 'package:meta/meta.dart'; + export 'src/builders/annotation.dart' show AnnotationBuilder; export 'src/builders/class.dart' show clazz, ClassBuilder; -export 'src/builders/expression.dart' show ExpressionBuilder; +export 'src/builders/expression.dart' show literal, ExpressionBuilder; export 'src/builders/reference.dart' show reference, ReferenceBuilder; export 'src/builders/shared.dart' show AstBuilder, Scope; export 'src/builders/statement.dart' show StatementBuilder; + +final _dartFmt = new DartFormatter(); + +/// Returns [source] formatted by `dartfmt`. +@visibleForTesting +String dartfmt(String source) { + try { + return _dartFmt.format(source); + } on FormatterException catch (_) { + return _dartFmt.formatStatement(source); + } +} diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index 34fd59f..d5760be 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -23,7 +23,7 @@ abstract class HasAnnotations implements AstBuilder { /// Implements [HasAnnotations]. abstract class HasAnnotationsMixin extends HasAnnotations { - final List _annotations = []; + final List _annotations = []; @override void addAnnotation(AnnotationBuilder annotation) { @@ -41,5 +41,7 @@ abstract class HasAnnotationsMixin extends HasAnnotations { } /// Returns a [List] of all built [Annotation]s. - List buildAnnotations([Scope scope]) => _annotations.map/**/((a) => a.buildAnnotation(scope)).toList(); + List buildAnnotations([Scope scope]) => _annotations + .map/**/((a) => a.buildAnnotation(scope)) + .toList(); } diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index c704728..65c9420 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -3,7 +3,10 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; /// Lazily builds an [Expression] AST when [buildExpression] is invoked. abstract class ExpressionBuilder implements AstBuilder { @@ -11,9 +14,12 @@ abstract class ExpressionBuilder implements AstBuilder { Expression buildExpression([Scope scope]); } +/// Implements much of [ExpressionBuilder]. +abstract class AbstractExpression implements ExpressionBuilder {} + /// An [AstBuilder] that can add [ExpressionBuilder]. abstract class HasExpressions implements AstBuilder { - final List _expressions = []; + final List _expressions = []; /// Adds [expression] to the builder. void addExpression(ExpressionBuilder expression) { @@ -34,5 +40,61 @@ abstract class HasExpressionsMixin extends HasExpressions { } /// Returns a [List] of all built [Expression]s. - List buildExpressions([Scope scope]) => _expressions.map/**/((e) => e.buildExpression(scope)).toList(); + List buildExpressions([Scope scope]) => _expressions + .map/**/((e) => e.buildExpression(scope)) + .toList(); +} + +final _null = new NullLiteral(new KeywordToken(Keyword.NULL, 0)); +final _true = new BooleanLiteral(new KeywordToken(Keyword.TRUE, 0), true); +final _false = new BooleanLiteral(new KeywordToken(Keyword.FALSE, 0), true); + +/// Returns a pre-defined literal expression of [value]. +/// +/// Only primitive values are allowed. +ExpressionBuilder literal(value) => new _LiteralExpression(_literal(value)); + +Literal _literal(value) { + if (value == null) { + return _null; + } else if (value is bool) { + return value ? _true : _false; + } else if (value is String) { + return new SimpleStringLiteral(stringToken("'$value'"), value); + } else if (value is int) { + return new IntegerLiteral(stringToken('$value'), value); + } else if (value is double) { + return new DoubleLiteral(stringToken('$value'), value); + } else if (value is List) { + return new ListLiteral( + null, + null, + $openBracket, + value.map/**/(_literal).toList(), + $closeBracket, + ); + } else if (value is Map) { + return new MapLiteral( + null, + null, + $openBracket, + value.keys.map/**/((k) { + return new MapLiteralEntry(_literal(k), $colon, _literal(value[k])); + }).toList(), + $closeBracket, + ); + } + throw new ArgumentError.value(value, 'Unsupported'); +} + +class _LiteralExpression extends Object with AbstractExpression { + final Literal _literal; + + _LiteralExpression(this._literal); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([_]) => _literal; } diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart index 0e29867..6d46a59 100644 --- a/lib/src/builders/shared.dart +++ b/lib/src/builders/shared.dart @@ -39,8 +39,8 @@ class _IdentityScope implements Scope { /// Returns a string [Literal] from [value]. Identifier stringIdentifier(String value) => new SimpleIdentifier( - stringToken(value), -); + stringToken(value), + ); /// Returns an [Identifier] for [name] via [scope]. /// diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart index 35625a3..bdd8eb6 100644 --- a/lib/src/builders/statement.dart +++ b/lib/src/builders/statement.dart @@ -13,7 +13,7 @@ abstract class StatementBuilder implements AstBuilder { /// An [AstBuilder] that can add [StatementBuilder]. abstract class HasStatements implements AstBuilder { - final List _statements = []; + final List _statements = []; /// Adds [statement] to the builder. void addStatement(StatementBuilder statement) { @@ -34,5 +34,6 @@ abstract class HasStatementsMixin extends HasStatements { } /// Returns a [List] of all built [Statement]s. - List buildStatements([Scope scope]) => _statements.map/**/((e) => e.buildStatement(scope)).toList(); + List buildStatements([Scope scope]) => + _statements.map/**/((e) => e.buildStatement(scope)).toList(); } diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index cacd355..c6d7c3d 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -14,6 +14,9 @@ final Token $assert = new KeywordToken(Keyword.ASSERT, 0); /// The `class` token. final Token $class = new KeywordToken(Keyword.CLASS, 0); +/// The `const` token. +final Token $const = new KeywordToken(Keyword.CONST, 0); + /// The `extends` token. final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); @@ -29,9 +32,6 @@ final Token $static = new KeywordToken(Keyword.STATIC, 0); /// The `final` token. final Token $final = new KeywordToken(Keyword.FINAL, 0); -/// The `const` token. -final Token $const = new KeywordToken(Keyword.CONST, 0); - /// The `var` token. final Token $var = new KeywordToken(Keyword.VAR, 0); @@ -76,6 +76,12 @@ final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); /// The '}' token. final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); +/// The '[` token. +final Token $openBracket = new Token(TokenType.OPEN_SQUARE_BRACKET, 0); + +/// The `]` token. +final Token $closeBracket = new Token(TokenType.CLOSE_SQUARE_BRACKET, 0); + /// The `>` token. final Token $gt = new Token(TokenType.GT, 0); diff --git a/lib/testing.dart b/lib/testing.dart index 3b0159d..e183425 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -2,9 +2,7 @@ // 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. -import 'package:analyzer/analyzer.dart'; import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/src/tokens.dart'; import 'package:dart_style/dart_style.dart'; import 'package:matcher/matcher.dart'; @@ -17,16 +15,16 @@ import 'package:matcher/matcher.dart'; /// over the default `Ast.toSource` implementation (i.e. adds new lines between /// methods in classes, and more). Matcher equalsSource( - String source, { - Scope scope: Scope.identity, - }) { + String source, { + Scope scope: Scope.identity, +}) { try { source = dartfmt(source); } on FormatterException catch (_) {} return new _EqualsSource( - scope, - source, - ); + scope, + source, + ); } /// Returns a [Matcher] that checks a [CodeBuilder versus [source]. @@ -36,13 +34,13 @@ Matcher equalsSource( /// /// **NOTE**: Whitespace is ignored. Matcher equalsUnformatted( - String source, { - Scope scope: Scope.identity, - }) { + String source, { + Scope scope: Scope.identity, +}) { return new _EqualsSource( - scope, - source, - ); + scope, + source, + ); } class _EqualsSource extends Matcher { @@ -58,19 +56,19 @@ class _EqualsSource extends Matcher { @override Description describeMismatch( - item, - Description mismatchDescription, - Map matchState, - bool verbose, - ) { + item, + Description mismatchDescription, + Map matchState, + bool verbose, + ) { if (item is AstBuilder) { var origin = _formatAst(item); return equalsIgnoringWhitespace(_source).describeMismatch( - origin, - mismatchDescription.addDescriptionOf(origin), - matchState, - verbose, - ); + origin, + mismatchDescription.addDescriptionOf(origin), + matchState, + verbose, + ); } else { return mismatchDescription.add('$item is not a CodeBuilder'); } @@ -86,6 +84,6 @@ class _EqualsSource extends Matcher { String _formatAst(AstBuilder builder) { var astNode = builder.buildAst(_scope); - return prettyToSource(astNode); + return /*prettyToSource(astNode)*/ astNode.toSource(); } } diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart index f776972..e3353b0 100644 --- a/test/builders/class_test.dart +++ b/test/builders/class_test.dart @@ -1,8 +1,28 @@ +// 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. + import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing.dart'; import 'package:test/test.dart'; void main() { test('should emit an empty class', () { + expect( + clazz('Animal'), + equalsSource(r''' + class Animal {} + '''), + ); + }); + test('should emit a class with an annotation', () { + expect( + clazz('Animal', [reference('deprecated')]), + equalsSource(r''' + @deprecated + class Animal {} + '''), + ); }); } diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart new file mode 100644 index 0000000..c60c96a --- /dev/null +++ b/test/builders/expression_test.dart @@ -0,0 +1,43 @@ +// 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. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group('literal', () { + test('should emit a null', () { + expect(literal(null), equalsSource(r'null')); + }); + + test('should emit true', () { + expect(literal(true), equalsSource(r'true')); + }); + + test('should emit false', () { + expect(literal(false), equalsSource(r'false')); + }); + + test('should emit an int', () { + expect(literal(5), equalsSource(r'5')); + }); + + test('should emit a double', () { + expect(literal(5.5), equalsSource(r'5.5')); + }); + + test('should emit a string', () { + expect(literal('Hello'), equalsSource(r"'Hello'")); + }); + + test('should emit a list', () { + expect(literal([1, 2, 3]), equalsSource(r'[1, 2, 3]')); + }); + + test('should emit a map', () { + expect(literal({1: 2, 2: 3}), equalsSource(r'{1 : 2, 2 : 3}')); + }); + }); +} diff --git a/test/builders/reference_test.dart b/test/builders/reference_test.dart new file mode 100644 index 0000000..f25ae74 --- /dev/null +++ b/test/builders/reference_test.dart @@ -0,0 +1,9 @@ +// 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. + +import 'package:test/test.dart'; + +void main() { + group('reference', () {}); +} diff --git a/test/builders/shared_test.dart b/test/builders/shared_test.dart index 1cf6215..f7013dd 100644 --- a/test/builders/shared_test.dart +++ b/test/builders/shared_test.dart @@ -1,3 +1,7 @@ +// 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. + import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/tokens.dart'; import 'package:test/test.dart'; From 0a181a04d45912529627ef2ab48d57038e23b5d2 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 14:02:35 -0700 Subject: [PATCH 07/19] . --- lib/src/pretty_printer.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/src/pretty_printer.dart diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart new file mode 100644 index 0000000..e69de29 From d808e6fb7100e18a3c21022b6840242538806327 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 14:03:30 -0700 Subject: [PATCH 08/19] . --- lib/src/pretty_printer.dart | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart index e69de29..f7e3047 100644 --- a/lib/src/pretty_printer.dart +++ b/lib/src/pretty_printer.dart @@ -0,0 +1,106 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; + +/// Augments [AstNode.toSource] by adding some whitespace/line breaks. +/// +/// The final result is run through `dartfmt`. +/// +/// This is the _recommended_ output (but not required) when comparing ASTs +/// to expected golden files/text blobs. +String prettyToSource(AstNode astNode) { + var buffer = new PrintBuffer(); + var visitor = new _PrettyToSourceVisitor(buffer); + astNode.accept(visitor); + var source = buffer.toString(); + try { + return dartfmt(source); + } on FormatterException catch (_) { + return source; + } +} + +// TODO(matanl): Remove copied-pasted methods when API becomes available. +// https://github.com/dart-lang/sdk/issues/27169 +class _PrettyToSourceVisitor extends ToSourceVisitor { + // https://github.com/dart-lang/sdk/issues/27301 + final StringBuffer _buffer; + + _PrettyToSourceVisitor(PrintBuffer buffer) + : _buffer = buffer, + super(buffer); + + @override + Object visitClassDeclaration(ClassDeclaration node) { + _visitNodeListWithSeparatorAndSuffix(node.metadata, " ", " "); + _visitTokenWithSuffix(node.abstractKeyword, " "); + _buffer.write("class "); + _visitNode(node.name); + _visitNode(node.typeParameters); + _visitNodeWithPrefix(" ", node.extendsClause); + _visitNodeWithPrefix(" ", node.withClause); + _visitNodeWithPrefix(" ", node.implementsClause); + _buffer.write(" {"); + _visitNodeListWithSeparator(node.members, "\n\n"); + _buffer.write("}"); + return null; + } + + // Safely visit the given [node]. + void _visitNode(AstNode node) { + if (node != null) { + node.accept(this); + } + } + + // Write a list of [nodes], separated by the given [separator]. + void _visitNodeListWithSeparator(NodeList nodes, String separator) { + if (nodes != null) { + int size = nodes.length; + for (int i = 0; i < size; i++) { + if (i > 0) { + _buffer.write(separator); + } + nodes[i].accept(this); + } + } + } + + // 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) { + if (nodes != null) { + int size = nodes.length; + if (size > 0) { + for (int i = 0; i < size; i++) { + if (i > 0) { + _buffer.write(separator); + } + nodes[i].accept(this); + } + _buffer.write(suffix); + } + } + } + + // Safely visit the given [node], writing the [prefix] before the node if it + // is non-`null`. + void _visitNodeWithPrefix(String prefix, AstNode node) { + if (node != null) { + _buffer.write(prefix); + node.accept(this); + } + } + + // Safely visit the given [token], writing the [suffix] after the token if it + // is non-`null`. + void _visitTokenWithSuffix(Token token, String suffix) { + if (token != null) { + _buffer.write(token.lexeme); + _buffer.write(suffix); + } + } +} From ad989ef6ce3b93f038945e9edd7bdf6a1d3a962c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sat, 15 Oct 2016 14:04:07 -0700 Subject: [PATCH 09/19] . --- lib/src/analyzer_patch.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/src/analyzer_patch.dart diff --git a/lib/src/analyzer_patch.dart b/lib/src/analyzer_patch.dart new file mode 100644 index 0000000..e69de29 From 597f6725845690a556ad635a8c450e57443bc642 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sun, 16 Oct 2016 12:18:39 -0700 Subject: [PATCH 10/19] Another checkpoint. --- lib/code_builder.dart | 5 +- lib/dart/core.dart | 23 +++ lib/src/analyzer_patch.dart | 65 ++++++++ lib/src/builders/annotation.dart | 4 +- lib/src/builders/class.dart | 178 +++++++++++++++++++-- lib/src/builders/expression.dart | 3 +- lib/src/builders/method.dart | 230 +++++++++++++++++++++++++++ lib/src/builders/parameter.dart | 207 ++++++++++++++++++++++++ lib/src/builders/reference.dart | 13 +- lib/src/builders/type.dart | 35 ++++ lib/src/pretty_printer.dart | 5 + lib/testing.dart | 4 +- test/builders/class_test.dart | 74 +++++++++ test/builders/method_test.dart | 124 +++++++++++++++ test/builders/parameter_builder.dart | 50 ++++++ 15 files changed, 1004 insertions(+), 16 deletions(-) create mode 100644 lib/dart/core.dart create mode 100644 lib/src/builders/method.dart create mode 100644 lib/src/builders/parameter.dart create mode 100644 lib/src/builders/type.dart create mode 100644 test/builders/method_test.dart create mode 100644 test/builders/parameter_builder.dart diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 2a320db..f76853b 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -6,8 +6,11 @@ import 'package:dart_style/dart_style.dart'; import 'package:meta/meta.dart'; export 'src/builders/annotation.dart' show AnnotationBuilder; -export 'src/builders/class.dart' show clazz, ClassBuilder; +export 'src/builders/class.dart' + show clazz, extend, implement, mixin, ClassBuilder; export 'src/builders/expression.dart' show literal, ExpressionBuilder; +export 'src/builders/method.dart' show constructor, constructorNamed, fieldFormal, method, named, ConstructorBuilder, MethodBuilder; +export 'src/builders/parameter.dart' show parameter, ParameterBuilder; export 'src/builders/reference.dart' show reference, ReferenceBuilder; export 'src/builders/shared.dart' show AstBuilder, Scope; export 'src/builders/statement.dart' show StatementBuilder; diff --git a/lib/dart/core.dart b/lib/dart/core.dart new file mode 100644 index 0000000..013fb0b --- /dev/null +++ b/lib/dart/core.dart @@ -0,0 +1,23 @@ +import 'package:code_builder/code_builder.dart'; + +/// Meta programming from `dart:core`. +final core = new _DartCore(); + +class _DartCore { + _DartCore(); + + /// Reference the core `int` type. + final Int = reference('int', 'dart:core'); + + /// Reference the core `List` type. + final List = reference('List', 'dart:core'); + + /// Reference the core `Object` type. + final Object = reference('Object', 'dart:core'); + + /// Reference the core `String` type. + final String = reference('String', 'dart:core'); + + /// Reference the core `void` type. + final Void = reference('void'); +} diff --git a/lib/src/analyzer_patch.dart b/lib/src/analyzer_patch.dart index e69de29..5b27d77 100644 --- a/lib/src/analyzer_patch.dart +++ b/lib/src/analyzer_patch.dart @@ -0,0 +1,65 @@ +// 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. + +import 'package:analyzer/src/generated/java_core.dart'; + +/// Implements both old-API [PrintWriter] and new-API [StringBuffer]. +/// +/// This makes it easier to re-use our `pretty_printer` until analyzer updates. +class PrintBuffer implements PrintWriter, StringBuffer { + final StringBuffer _impl = new StringBuffer(); + + @override + void clear() {} + + @override + bool get isEmpty => _impl.isEmpty; + + @override + bool get isNotEmpty => _impl.isNotEmpty; + + @override + int get length => _impl.length; + + @override + void newLine() { + _impl.writeln(); + } + + @override + void print(x) { + _impl.write(x); + } + + @override + void printf(String fmt, List args) => throw new UnimplementedError(); + + @override + void println(String s) { + _impl.writeln(s); + } + + @override + void write(Object obj) { + _impl.write(obj); + } + + @override + void writeAll(Iterable objects, [String separator = ""]) { + _impl.writeAll(objects); + } + + @override + void writeCharCode(int charCode) { + _impl.writeCharCode(charCode); + } + + @override + void writeln([Object obj = ""]) { + _impl.writeln(obj); + } + + @override + String toString() => _impl.toString(); +} diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index d5760be..8b1b660 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -4,10 +4,12 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/parameter.dart'; import 'package:code_builder/src/builders/shared.dart'; /// Lazily builds an [Annotation] AST when [buildAnnotation] is invoked. -abstract class AnnotationBuilder implements ValidClassMember { +abstract class AnnotationBuilder + implements ValidClassMember, ValidParameterMember { /// Returns an [Annotation] AST representing the builder. Annotation buildAnnotation([Scope scope]); } diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index 7b1ba11..1457109 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -3,8 +3,12 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/dart/core.dart'; import 'package:code_builder/src/builders/annotation.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'; +import 'package:code_builder/src/tokens.dart'; /// A more short-hand way of constructing a [ClassBuilder]. ClassBuilder clazz( @@ -15,6 +19,16 @@ ClassBuilder clazz( for (final member in members) { if (member is AnnotationBuilder) { clazz.addAnnotation(member); + } else if (member is _TypeNameWrapper) { + if (member.extend) { + clazz.setExtends(member.type); + } else if (member.mixin) { + clazz.addMixin(member.type); + } else { + clazz.addImplement(member.type); + } + } else if (member is ConstructorBuilder) { + clazz.addConstructor(member); } else { throw new StateError('Invalid AST type: ${member.runtimeType}'); } @@ -22,14 +36,82 @@ ClassBuilder clazz( return clazz; } +/// Returns a wrapper around [type] for use with [clazz]. +_TypeNameWrapper extend(TypeBuilder type) { + return new _TypeNameWrapper( + type, + extend: true, + ); +} + +/// Returns a wrapper around [type] for use with [clazz]. +_TypeNameWrapper implement(TypeBuilder type) { + return new _TypeNameWrapper( + type, + implement: true, + ); +} + +/// Returns a wrapper around [type] for use with [clazz]. +_TypeNameWrapper mixin(TypeBuilder type) { + return new _TypeNameWrapper( + type, + mixin: true, + ); +} + +class _TypeNameWrapper implements ValidClassMember { + final bool extend; + final bool implement; + final bool mixin; + final TypeBuilder type; + + _TypeNameWrapper( + this.type, { + this.extend: false, + this.implement: false, + this.mixin: false, + }); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within clazz'); +} + /// Lazily builds an [ClassDeclaration] AST when [buildClass] is invoked. abstract class ClassBuilder - implements AstBuilder, HasAnnotations { + implements + AstBuilder, + HasAnnotations, + TypeBuilder { /// Returns a new [ClassBuilder] with [name]. - factory ClassBuilder(String name) = _ClassBuilderImpl; + factory ClassBuilder( + String name, { + bool asAbstract, + TypeBuilder asExtends, + Iterable asWith, + Iterable asImplements, + }) = _ClassBuilderImpl; /// Returns an [ClassDeclaration] AST representing the builder. ClassDeclaration buildClass([Scope scope]); + + /// Adds a [constructor]. + void addConstructor(ConstructorBuilder constructor); + + /// Adds an [interface] to implement. + void addImplement(TypeBuilder interface); + + /// Adds [interfaces] to implement. + void addImplements(Iterable interfaces); + + /// Adds a [mixin]. + void addMixin(TypeBuilder mixin); + + /// Adds [mixins]. + void addMixins(Iterable mixins); + + /// Sets [extend]. + void setExtends(TypeBuilder extend); } /// A marker interface for an AST that could be added to [ClassBuilder]. @@ -38,28 +120,106 @@ abstract class ValidClassMember implements AstBuilder {} class _ClassBuilderImpl extends Object with HasAnnotationsMixin implements ClassBuilder { + final _constructors = []; + + TypeBuilder _extends; + final List _with; + final List _implements; + + final bool _asAbstract; final String _name; - _ClassBuilderImpl(this._name); + _ClassBuilderImpl( + this._name, { + bool asAbstract: false, + TypeBuilder asExtends, + Iterable asWith: const [], + Iterable asImplements: const [], + }) + : _asAbstract = asAbstract, + _extends = asExtends, + _with = asWith.toList(), + _implements = asImplements.toList(); + + @override + void addConstructor(ConstructorBuilder constructor) { + _constructors.add(constructor); + } + + @override + void addImplement(TypeBuilder interface) { + _implements.add(interface); + } + + @override + void addImplements(Iterable interfaces) { + _implements.addAll(interfaces); + } + + @override + void addMixin(TypeBuilder mixin) { + _with.add(mixin); + } + + @override + void addMixins(Iterable mixins) { + _with.addAll(mixins); + } @override ClassDeclaration buildAst([Scope scope]) => buildClass(scope); @override ClassDeclaration buildClass([Scope scope]) { - return new ClassDeclaration( + var extend = _extends; + if (extend == null && _with.isNotEmpty) { + extend = core.Object; + } + final clazz = new ClassDeclaration( null, buildAnnotations(scope), - null, - null, + _asAbstract ? $abstract : null, + $class, stringIdentifier(_name), null, - null, - null, - null, + extend != null + ? new ExtendsClause( + $extends, + extend.buildType(scope), + ) + : null, + _with.isNotEmpty + ? new WithClause( + $with, + _with.map/**/((w) => w.buildType(scope)).toList(), + ) + : null, + _implements.isNotEmpty + ? new ImplementsClause( + $implements, + _implements.map/**/((i) => i.buildType(scope)).toList(), + ) + : null, null, null, null, ); + _constructors.forEach((constructor) { + clazz.members.add(constructor.buildConstructor( + this, + scope, + )); + }); + return clazz; + } + + @override + void setExtends(TypeBuilder extend) { + _extends = extend; + } + + @override + TypeName buildType([Scope scope]) { + return new TypeBuilder(_name).buildType(scope); } } diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 65c9420..6491fde 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -5,11 +5,12 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/src/builders/parameter.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/tokens.dart'; /// Lazily builds an [Expression] AST when [buildExpression] is invoked. -abstract class ExpressionBuilder implements AstBuilder { +abstract class ExpressionBuilder implements AstBuilder, ValidParameterMember { /// Returns an [Expression] AST representing the builder. Expression buildExpression([Scope scope]); } diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart new file mode 100644 index 0000000..870276e --- /dev/null +++ b/lib/src/builders/method.dart @@ -0,0 +1,230 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/parameter.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// A more short-hand way of constructing a [MethodBuilder]. +MethodBuilder method( + String name, [ + Iterable members = const [], +]) { + final List positional = []; + final List<_NamedParameterWrapper> named = <_NamedParameterWrapper>[]; + TypeBuilder returnType; + for (final member in members) { + if (member is TypeBuilder) { + returnType = member; + } else if (member is ParameterBuilder) { + positional.add(member); + } else if (member is _NamedParameterWrapper) { + named.add(member); + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + final method = new _MethodBuilderImpl( + name, + returns: returnType, + ); + positional.forEach(method.addPositional); + named.forEach((p) => method.addNamed(p._parameter)); + return method; +} + +/// Returns a wrapper around [parameter] for use with [method]. +_NamedParameterWrapper named(ParameterBuilder parameter) { + return new _NamedParameterWrapper(parameter); +} + +class _NamedParameterWrapper implements ValidConstructorMember, ValidMethodMember { + final ParameterBuilder _parameter; + + _NamedParameterWrapper(this._parameter); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); +} + +/// Lazily builds a method/function AST when the builder is invoked. +abstract class MethodBuilder implements HasAnnotations, HasParameters { + /// Creates a new [MethodBuilder]. + factory MethodBuilder(String name) = _MethodBuilderImpl; + + /// Creates a new [MethodBuilder] that returns `void`. + factory MethodBuilder.returnVoid(String name) { + return new _MethodBuilderImpl(name, returns: core.Void); + } + + /// Returns a [FunctionDeclaration] AST representing the builder. + FunctionDeclaration buildFunction([Scope scope]); + + /// Returns an [MethodDeclaration] AST representing the builder. + MethodDeclaration buildMethod([Scope scope]); +} + +/// A marker interface for an AST that could be added to [MethodBuilder]. +abstract class ValidMethodMember implements AstBuilder {} + +class _MethodBuilderImpl extends Object + with HasAnnotationsMixin, HasParametersMixin + implements MethodBuilder { + final String _name; + final TypeBuilder _returnType; + + _MethodBuilderImpl(this._name, {TypeBuilder returns}) : _returnType = returns; + + @override + AstNode buildAst([Scope scope]) => buildFunction(scope); + + @override + FunctionDeclaration buildFunction([Scope scope]) { + return new FunctionDeclaration( + null, + buildAnnotations(scope), + null, + _returnType?.buildType(scope), + null, + getIdentifier(scope, _name), + new FunctionExpression( + null, + buildParameterList(scope), + new EmptyFunctionBody($semicolon), + ), + ); + } + + @override + MethodDeclaration buildMethod([Scope scope]) { + return new MethodDeclaration( + null, + buildAnnotations(scope), + null, + null, + _returnType?.buildType(scope), + null, + null, + getIdentifier(scope, _name), + null, + buildParameterList(scope), + new EmptyFunctionBody($semicolon), + ); + } +} + +/// Returns a wrapper around [parameter] for use with [constructor]. +_FieldParameterWrapper fieldFormal(Object parameter) { + assert(parameter is ParameterBuilder || parameter is _NamedParameterWrapper); + return new _FieldParameterWrapper(parameter); +} + +class _FieldParameterWrapper implements ValidConstructorMember, ValidMethodMember { + final Object /*ParameterBuilder|_NamedParameterWrapper*/ _parameter; + + _FieldParameterWrapper(this._parameter); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); +} + +/// Short-hand for `new ConstructorBuilder(...)`. +ConstructorBuilder constructor([Iterable members = const []]) { + return _constructorImpl(members: members); +} + +/// Short-hand for `new ConstructorBuilder(name)`. +ConstructorBuilder constructorNamed(String name, [Iterable members = const []]) { + return _constructorImpl(name: name, members: members); +} + +typedef void _AddParameter(ConstructorBuilder constructor); + +ConstructorBuilder _constructorImpl({ + Iterable members, + String name, +}) { + + final List<_AddParameter> _addFunctions = <_AddParameter> []; + for (final member in members) { + if (member is ParameterBuilder) { + _addFunctions.add((c) => c.addPositional(member)); + } else if (member is _NamedParameterWrapper) { + _addFunctions.add((c) => c.addNamed(member._parameter)); + } else if (member is _FieldParameterWrapper) { + if (member._parameter is _NamedParameterWrapper) { + _NamedParameterWrapper p = member._parameter; + _addFunctions.add((c) => c.addNamed(p._parameter, asField: true)); + } else if (member._parameter is ParameterBuilder) { + _addFunctions.add((c) => c.addPositional(member._parameter, asField: true)); + } + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + final constructor = new ConstructorBuilder(name); + _addFunctions.forEach((a) => a(constructor)); + return constructor; +} + +/// A marker interface for an AST that could be added to [ConstructorBuilder]. +abstract class ValidConstructorMember implements ValidMethodMember {} + +/// Lazily builds an [ConstructorBuilder] AST when built. +abstract class ConstructorBuilder + implements + AstBuilder, + HasParameters, + ValidClassMember { + /// Create a new [ConstructorBuilder], optionally with a [name]. + factory ConstructorBuilder([String name]) = _NormalConstructorBuilder; + + @override + void addNamed(ParameterBuilder parameter, {bool asField: false}); + + @override + void addPositional(ParameterBuilder parameter, {bool asField: false}); + + /// Returns an [ConstructorDeclaration] AST representing the builder. + ConstructorDeclaration buildConstructor(TypeBuilder returnType, [Scope scope]); +} + +class _NormalConstructorBuilder + extends Object + with HasAnnotationsMixin, HasParametersMixin + implements ConstructorBuilder { + final String _name; + + _NormalConstructorBuilder([this._name]); + + @override + ConstructorDeclaration buildAst([Scope scope]) { + throw new UnsupportedError('Can only be built as part of a class.'); + } + + @override + ConstructorDeclaration buildConstructor(TypeBuilder returnType, [Scope scope]) { + return new ConstructorDeclaration( + null, + buildAnnotations(scope), + null, + null, + null, + returnType.buildType().name, + _name != null ? $period : null, + _name != null ? stringIdentifier(_name) : null, + buildParameterList(scope), + null, + null, + null, + new EmptyFunctionBody($semicolon), + ); + } +} diff --git a/lib/src/builders/parameter.dart b/lib/src/builders/parameter.dart new file mode 100644 index 0000000..ebab326 --- /dev/null +++ b/lib/src/builders/parameter.dart @@ -0,0 +1,207 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/annotation.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'; +import 'package:code_builder/src/tokens.dart'; + +/// A more short-hand way of constructing a [ParameterBuilder]. +ParameterBuilder parameter( + String name, [ + Iterable members = const [], +]) { + final List annotations = []; + ExpressionBuilder defaultTo; + bool defaultToSet = false; + TypeBuilder type; + for (final member in members) { + if (member is TypeBuilder) { + type = member; + } else if (member is AnnotationBuilder) { + annotations.add(member); + } else if (member is ExpressionBuilder) { + defaultTo = member; + defaultToSet = true; + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + var builder = new ParameterBuilder( + name, + type: type, + )..addAnnotations(annotations); + if (defaultToSet) { + builder = builder.asOptional(defaultTo); + } + return builder; +} + +/// A marker interface for an AST that could be added to [ParameterBuilder]. +abstract class ValidParameterMember implements AstBuilder {} + +/// Lazily builds an [FormalParameter] AST the builder is invoked. +abstract class ParameterBuilder + implements AstBuilder, HasAnnotations, ValidConstructorMember, ValidMethodMember { + /// Create a new builder for parameter [name]. + factory ParameterBuilder( + String name, { + TypeBuilder type, + }) = _SimpleParameterBuilder; + + /// Returns as an optional [ParameterBuilder] set to [defaultTo]. + ParameterBuilder asOptional([ExpressionBuilder defaultTo]); + + /// Returns a positional [FormalParameter] AST representing the builder. + FormalParameter buildPositional(bool field, [Scope scope]); + + /// Returns a positional [FormalParameter] AST representing the builder. + FormalParameter buildNamed(bool field, [Scope scope]); +} + +/// An [AstBuilder] that can be built up using [ParameterBuilder]. +abstract class HasParameters implements AstBuilder { + /// Adds [parameter] to the builder. + void addNamed(ParameterBuilder parameter); + + /// Adds [parameter] to the builder. + void addPositional(ParameterBuilder parameter); +} + +class _ParameterPair { + final bool _isField; + final bool _isNamed; + final ParameterBuilder _parameter; + + _ParameterPair(this._parameter, {bool field: false}) + : _isNamed = false, + _isField = field; + + _ParameterPair.named(this._parameter, {bool field: false}) + : _isNamed = true, + _isField = field; + + FormalParameter buildParameter([Scope scope]) { + return _isNamed + ? _parameter.buildNamed(_isField, scope) + : _parameter.buildPositional(_isField, scope); + } +} + +/// Implements [HasParameters]. +abstract class HasParametersMixin implements HasParameters { + final List<_ParameterPair> _parameters = <_ParameterPair> []; + + @override + void addNamed(ParameterBuilder parameter, {bool asField: false}) { + _parameters.add(new _ParameterPair.named(parameter, field: asField)); + } + + @override + void addPositional(ParameterBuilder parameter, {bool asField: false}) { + _parameters.add(new _ParameterPair(parameter, field: asField)); + } + + /// Builds a [FormalParameterList]. + FormalParameterList buildParameterList([Scope scope]) { + return new FormalParameterList( + $openParen, + _parameters.map/**/((p) => p.buildParameter(scope)).toList(), + null, + null, + $closeParen, + ); + } +} + +class _OptionalParameterBuilder extends Object + with HasAnnotationsMixin + implements ParameterBuilder { + final ParameterBuilder _parameter; + final ExpressionBuilder _expression; + + _OptionalParameterBuilder(this._parameter, [this._expression]); + + @override + ParameterBuilder asOptional([ExpressionBuilder defaultTo]) { + return new _OptionalParameterBuilder(_parameter, defaultTo); + } + + @override + FormalParameter buildAst([Scope scope]) => buildPositional(false, scope); + + @override + FormalParameter buildNamed(bool field, [Scope scope]) { + return new DefaultFormalParameter( + _parameter.buildPositional(field, scope), + ParameterKind.NAMED, + _expression != null ? $colon : null, + _expression?.buildExpression(scope), + ); + } + + @override + FormalParameter buildPositional(bool field, [Scope scope]) { + return new DefaultFormalParameter( + _parameter.buildPositional(field, scope), + ParameterKind.POSITIONAL, + _expression != null ? $equals : null, + _expression?.buildExpression(scope), + ); + } +} + +class _SimpleParameterBuilder extends Object + with HasAnnotationsMixin + implements ParameterBuilder { + final String _name; + final TypeBuilder _type; + + _SimpleParameterBuilder( + String name, { + TypeBuilder type, + }) + : _name = name, + _type = type; + + @override + ParameterBuilder asOptional([ExpressionBuilder defaultTo]) { + return new _OptionalParameterBuilder(this, defaultTo); + } + + @override + FormalParameter buildAst([Scope scope]) => buildPositional(false, scope); + + @override + FormalParameter buildPositional(bool field, [Scope scope]) { + if (field) { + return new FieldFormalParameter( + null, + buildAnnotations(scope), + null, + _type?.buildType(scope), + $this, + $period, + stringIdentifier(_name), + null, + null, + ); + } + return new SimpleFormalParameter( + null, + buildAnnotations(scope), + null, + _type?.buildType(scope), + stringIdentifier(_name), + ); + } + + @override + FormalParameter buildNamed(bool field, [Scope scope]) { + return asOptional().buildNamed(field, scope); + } +} diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index 36f5f8f..7b2968b 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -5,18 +5,20 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/type.dart'; import 'package:code_builder/src/tokens.dart'; /// Creates a reference called [name]. ReferenceBuilder reference(String name, [String importUri]) { - return new ReferenceBuilder._(name); + return new ReferenceBuilder._(name, importUri); } /// An abstract way of representing other types of [AstBuilder]. -class ReferenceBuilder implements AnnotationBuilder { +class ReferenceBuilder implements AnnotationBuilder, TypeBuilder { + final String _importFrom; final String _name; - ReferenceBuilder._(this._name); + ReferenceBuilder._(this._name, [this._importFrom]); @override Annotation buildAnnotation([Scope scope]) { @@ -31,4 +33,9 @@ class ReferenceBuilder implements AnnotationBuilder { @override AstNode buildAst([Scope scope]) => throw new UnimplementedError(); + + @override + TypeName buildType([Scope scope]) { + return new TypeBuilder(_name, _importFrom).buildType(scope); + } } diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart new file mode 100644 index 0000000..c14e243 --- /dev/null +++ b/lib/src/builders/type.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/src/builders/parameter.dart'; +import 'package:code_builder/src/builders/shared.dart'; + +/// Lazily builds an [TypeName] AST when [buildType] is invoked. +class TypeBuilder + implements AstBuilder, ValidMethodMember, ValidParameterMember { + final String _importFrom; + final String _name; + + /// Creates a new [TypeBuilder]. + factory TypeBuilder(String name, [String importFrom]) = TypeBuilder._; + + TypeBuilder._(this._name, [this._importFrom]); + + @override + AstNode buildAst([Scope scope]) => buildType(scope); + + /// Returns an [TypeName] AST representing the builder. + TypeName buildType([Scope scope]) { + return new TypeName( + getIdentifier( + scope, + _name, + _importFrom != null ? Uri.parse(_importFrom) : null, + ), + null, + ); + } +} diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart index f7e3047..2a077ee 100644 --- a/lib/src/pretty_printer.dart +++ b/lib/src/pretty_printer.dart @@ -3,6 +3,11 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dart_style/dart_style.dart'; + +import 'analyzer_patch.dart'; /// Augments [AstNode.toSource] by adding some whitespace/line breaks. /// diff --git a/lib/testing.dart b/lib/testing.dart index e183425..1ea0954 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -6,6 +6,8 @@ import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:matcher/matcher.dart'; +import 'src/pretty_printer.dart'; + /// Returns a [Matcher] that checks an [AstBuilder] versus [source]. /// /// On failure, uses the default string matcher to show a detailed diff between @@ -84,6 +86,6 @@ class _EqualsSource extends Matcher { String _formatAst(AstBuilder builder) { var astNode = builder.buildAst(_scope); - return /*prettyToSource(astNode)*/ astNode.toSource(); + return prettyToSource(astNode); } } diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart index e3353b0..8becd15 100644 --- a/test/builders/class_test.dart +++ b/test/builders/class_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; import 'package:code_builder/testing.dart'; import 'package:test/test.dart'; @@ -25,4 +26,77 @@ void main() { '''), ); }); + + test('should emit a class that extends another', () { + expect( + clazz('Animal', [extend(reference('Life'))]), + equalsSource(r''' + class Animal extends Life {} + '''), + ); + }); + + test('should emit a class that implements another', () { + expect( + clazz('Animal', [implement(reference('Living'))]), + equalsSource(r''' + class Animal implements Living {} + '''), + ); + }); + + test('should emit a class that mixes in another', () { + expect( + clazz('Animal', [mixin(reference('Living'))]), + equalsSource(r''' + class Animal extends Object with Living {} + '''), + ); + }); + + test('should emit a class with a constructor', () { + expect( + clazz('Animal', [ + constructor(), + ]), + equalsSource(r''' + class Animal { + Animal(); + } + '''), + ); + }); + + test('should emit a class with a named constructor', () { + expect( + clazz('Animal', [ + constructorNamed('internal'), + ]), + equalsSource(r''' + class Animal { + Animal.internal(); + } + '''), + ); + }); + + test('should emit a class with a constructor with parameters', () { + expect( + clazz('Animal', [ + constructor([ + parameter('name', [core.String]), + fieldFormal( + named( + parameter('age').asOptional(literal(0)), + ), + ) + ]) + ]), + equalsSource(r''' + class Animal { + Animal(String name, {this.age: 0}); + } + '''), + ); + }); } diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart new file mode 100644 index 0000000..7dec944 --- /dev/null +++ b/test/builders/method_test.dart @@ -0,0 +1,124 @@ +// 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. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a top-level main() function', () { + expect( + method('main'), + equalsSource(r''' + main(); + '''), + ); + }); + + test('should emit a top-level void main() function', () { + expect( + method('main', [ + core.Void, + ]), + equalsSource(r''' + void main(); + '''), + ); + }); + + test('should emit a function with a parameter', () { + expect( + method('main', [ + parameter('args', [core.List]), + ]), + equalsSource(r''' + main(List args); + '''), + ); + }); + + test('should emit a function with multiple parameters', () { + expect( + method('main', [ + parameter('a'), + parameter('b'), + parameter('c').asOptional(), + ]), + equalsSource(r''' + main(a, b, [c]); + '''), + ); + }); + + test('should emit a function with multiple parameters', () { + expect( + method('main', [ + parameter('a'), + parameter('b'), + parameter('c').asOptional(literal(true)), + ]), + equalsSource(r''' + main(a, b, [c = true]); + '''), + ); + }); + + test('should emit a function with named parameters', () { + expect( + method('main', [ + named(parameter('a')), + named(parameter('b').asOptional(literal(true))), + ]), + equalsSource(r''' + main({a, b : true}); + '''), + ); + }); + + group('constructors', () { + test('should emit a simple constructor', () { + expect( + new ConstructorBuilder() + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + Animal(); + '''), + ); + }); + + test('should emit a simple constructor with parameters', () { + expect( + (new ConstructorBuilder()..addPositional(parameter('name'))) + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + Animal(name); + '''), + ); + }); + + test('should emit a simple constructor with field-formal parameters', () { + expect( + (new ConstructorBuilder() + ..addPositional( + parameter('name'), + asField: true, + )) + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + Animal(this.name); + '''), + ); + }); + }); +} diff --git a/test/builders/parameter_builder.dart b/test/builders/parameter_builder.dart new file mode 100644 index 0000000..8ac2d81 --- /dev/null +++ b/test/builders/parameter_builder.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a simple parameter', () { + expect( + parameter('foo'), + equalsSource(r''' + foo + '''), + ); + }); + + test('should emit a typed parameter', () { + expect( + parameter('foo', [reference('String')]), + equalsSource(r''' + String foo + '''), + ); + }); + + test('should emit an optional parameter with a default value', () { + expect( + parameter('foo').asOptional(literal(true)), + equalsSource(r''' + foo = true + '''), + ); + }); + + test('should emit a named parameter with a default value', () { + expect( + parameter('foo').asOptional(literal(true)).buildNamed(false).toSource(), + equalsIgnoringCase(r'foo : true'), + ); + }); + + test('should emit a field formal parameter', () { + expect( + parameter('foo').buildPositional(true).toSource(), + equalsIgnoringCase(r'this.foo'), + ); + }); +} From daabd5185bec598008d43423c6943edf3826e7a2 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Sun, 16 Oct 2016 15:37:08 -0700 Subject: [PATCH 11/19] Another checkpoint with if statements. --- lib/code_builder.dart | 4 +- lib/dart/core.dart | 16 +- lib/src/builders/expression.dart | 247 +++++++++++++++++++- lib/src/builders/expression/assert.dart | 23 ++ lib/src/builders/expression/assign.dart | 52 +++++ lib/src/builders/expression/invocation.dart | 93 ++++++++ lib/src/builders/expression/negate.dart | 18 ++ lib/src/builders/expression/operators.dart | 21 ++ lib/src/builders/expression/return.dart | 19 ++ lib/src/builders/method.dart | 2 +- lib/src/builders/reference.dart | 17 +- lib/src/builders/statement.dart | 39 +++- lib/src/builders/statement/block.dart | 29 +++ lib/src/builders/statement/if.dart | 94 ++++++++ lib/src/tokens.dart | 11 +- test/builders/expression_test.dart | 186 +++++++++++++++ test/builders/method_test.dart | 2 +- test/builders/statement_test.dart | 88 +++++++ 18 files changed, 942 insertions(+), 19 deletions(-) create mode 100644 lib/src/builders/expression/assert.dart create mode 100644 lib/src/builders/expression/assign.dart create mode 100644 lib/src/builders/expression/invocation.dart create mode 100644 lib/src/builders/expression/negate.dart create mode 100644 lib/src/builders/expression/operators.dart create mode 100644 lib/src/builders/expression/return.dart create mode 100644 lib/src/builders/statement/block.dart create mode 100644 lib/src/builders/statement/if.dart create mode 100644 test/builders/statement_test.dart diff --git a/lib/code_builder.dart b/lib/code_builder.dart index f76853b..54eaec4 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -11,9 +11,9 @@ export 'src/builders/class.dart' export 'src/builders/expression.dart' show literal, ExpressionBuilder; export 'src/builders/method.dart' show constructor, constructorNamed, fieldFormal, method, named, ConstructorBuilder, MethodBuilder; export 'src/builders/parameter.dart' show parameter, ParameterBuilder; -export 'src/builders/reference.dart' show reference, ReferenceBuilder; +export 'src/builders/reference.dart' show explicitThis, reference, ReferenceBuilder; export 'src/builders/shared.dart' show AstBuilder, Scope; -export 'src/builders/statement.dart' show StatementBuilder; +export 'src/builders/statement.dart' show ifThen, elseIf, elseThen, IfStatementBuilder, StatementBuilder; final _dartFmt = new DartFormatter(); diff --git a/lib/dart/core.dart b/lib/dart/core.dart index 013fb0b..37c62de 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -3,11 +3,21 @@ import 'package:code_builder/code_builder.dart'; /// Meta programming from `dart:core`. final core = new _DartCore(); +// Forcibly namespaces to avoid conflicts when importing this library. class _DartCore { _DartCore(); + /// Reference the core `bool` type. + final bool = reference('bool', 'dart:core'); + + /// Reference the core `identical` function. + final identical = reference('identical', 'dart:core'); + + /// Reference the core `print` function. + final print = reference('print'); + /// Reference the core `int` type. - final Int = reference('int', 'dart:core'); + final int = reference('int', 'dart:core'); /// Reference the core `List` type. final List = reference('List', 'dart:core'); @@ -19,5 +29,7 @@ class _DartCore { final String = reference('String', 'dart:core'); /// Reference the core `void` type. - final Void = reference('void'); + /// + /// **NOTE**: As a language limitation, this cannot be named `void`. + final $void = reference('void'); } diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 6491fde..2d1da83 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -1,3 +1,5 @@ +library code_builder.src.builders.expression; + // 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. @@ -5,18 +7,222 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/dart/core.dart'; import 'package:code_builder/src/builders/parameter.dart'; import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/statement/if.dart'; +import 'package:code_builder/src/builders/type.dart'; import 'package:code_builder/src/tokens.dart'; +part 'expression/assert.dart'; +part 'expression/assign.dart'; +part 'expression/invocation.dart'; +part 'expression/negate.dart'; +part 'expression/operators.dart'; +part 'expression/return.dart'; + /// Lazily builds an [Expression] AST when [buildExpression] is invoked. -abstract class ExpressionBuilder implements AstBuilder, ValidParameterMember { +abstract class ExpressionBuilder + implements AstBuilder, StatementBuilder, ValidParameterMember { /// Returns an [Expression] AST representing the builder. Expression buildExpression([Scope scope]); + + /// Return as a [StatementBuilder] that `assert`s this expression. + StatementBuilder asAssert(); + + /// Returns as a [StatementBuilder] that assigns to an existing [variable]. + StatementBuilder asAssign(String variable); + + /// Returns as a [StatementBuilder] that assigns to a new `const` [variable]. + StatementBuilder asConst(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that assigns to a new `final` [variable]. + StatementBuilder asFinal(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that assigns to a new `var` [variable]. + /// + /// If [type] is supplied, the resulting statement is `{type} {variable} =`. + StatementBuilder asVar(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that builds an `if` statement. + /*If*/ StatementBuilder asIf(); + + /// Returns as a [StatementBuilder] that `return`s this expression. + StatementBuilder asReturn(); + + /// Returns _explicitly_ as a [StatementBuilder]. + /// + /// **NOTE**: [ExpressionBuilder] is _already_ usable as a [StatementBuilder] + /// directly; this API exists in order force [buildAst] to return a + /// [Statement] AST instead of an expression. + StatementBuilder asStatement(); + + /// Returns as an [InvocationBuilder] with arguments added. + InvocationBuilder call( + Iterable positionalArguments, [ + Map namedArguments, + ]); + + /// Returns as an [InvocationBuilder] on [method] of this expression. + InvocationBuilder invoke( + String method, + Iterable positionalArguments, [ + Map namedArguments, + ]); + + /// Returns as an [ExpressionBuilder] comparing using `==` against [other]. + ExpressionBuilder equals(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] comparing using `identical`. + ExpressionBuilder identical(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] comparing using `!=` against [other]. + ExpressionBuilder notEquals(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] negating using the `!` operator. + ExpressionBuilder negate(); + + /// Returns as an [ExpressionBuilder] adding [other]. + ExpressionBuilder operator +(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] subtracting [other]. + ExpressionBuilder operator -(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] dividing by [other]. + ExpressionBuilder operator /(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] multiplying by [other]. + ExpressionBuilder operator *(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] wrapped in parentheses. + ExpressionBuilder parentheses(); } /// Implements much of [ExpressionBuilder]. -abstract class AbstractExpression implements ExpressionBuilder {} +abstract class AbstractExpressionMixin implements ExpressionBuilder { + @override + StatementBuilder asAssert() => new _AsAssert(this); + + @override + StatementBuilder asAssign(String variable) => new _AsAssign(this, variable); + + @override + StatementBuilder asConst(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $const); + } + + @override + StatementBuilder asFinal(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $final); + } + + @override + StatementBuilder asVar(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $var); + } + + @override + IfStatementBuilder asIf() => new IfStatementBuilder(this); + + @override + StatementBuilder asReturn() => new _AsReturn(this); + + @override + StatementBuilder asStatement() => new _AsStatement(this); + + @override + Statement buildStatement([Scope scope]) { + return asStatement().buildStatement(scope); + } + + @override + InvocationBuilder call( + Iterable positionalArguments, [ + Map namedArguments = const {}, + ]) { + final invocation = new InvocationBuilder._(this); + positionalArguments.forEach(invocation.addPositionalArgument); + namedArguments.forEach(invocation.addNamedArgument); + return invocation; + } + + @override + InvocationBuilder invoke( + String method, + Iterable positionalArguments, [ + Map namedArguments = const {}, + ]) { + final invocation = new InvocationBuilder._on(this, method); + positionalArguments.forEach(invocation.addPositionalArgument); + namedArguments.forEach(invocation.addNamedArgument); + return invocation; + } + + @override + ExpressionBuilder equals(ExpressionBuilder other) { + return new _AsBinaryExpression(this, other, $equalsEquals,); + } + + @override + ExpressionBuilder identical(ExpressionBuilder other) { + return core.identical.call([ + this, + other, + ]); + } + + @override + ExpressionBuilder notEquals(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $notEquals, + ); + } + + @override + ExpressionBuilder negate() => new _NegateExpression(this); + + @override + ExpressionBuilder operator +(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $plus, + ); + } + + @override + ExpressionBuilder operator -(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $minus, + ); + } + + @override + ExpressionBuilder operator /(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $divide, + ); + } + + @override + ExpressionBuilder operator *(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $multiply, + ); + } + + @override + ExpressionBuilder parentheses() => new _ParenthesesExpression(this); +} /// An [AstBuilder] that can add [ExpressionBuilder]. abstract class HasExpressions implements AstBuilder { @@ -88,7 +294,24 @@ Literal _literal(value) { throw new ArgumentError.value(value, 'Unsupported'); } -class _LiteralExpression extends Object with AbstractExpression { +class _AsStatement implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsStatement(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new ExpressionStatement( + _expression.buildExpression(scope), + $semicolon, + ); + } +} + +class _LiteralExpression extends Object with AbstractExpressionMixin { final Literal _literal; _LiteralExpression(this._literal); @@ -99,3 +322,21 @@ class _LiteralExpression extends Object with AbstractExpression { @override Expression buildExpression([_]) => _literal; } + +class _ParenthesesExpression extends Object with AbstractExpressionMixin { + final ExpressionBuilder _expression; + + _ParenthesesExpression(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new ParenthesizedExpression( + $openParen, + _expression.buildExpression(scope), + $closeParen, + ); + } +} diff --git a/lib/src/builders/expression/assert.dart b/lib/src/builders/expression/assert.dart new file mode 100644 index 0000000..3313fee --- /dev/null +++ b/lib/src/builders/expression/assert.dart @@ -0,0 +1,23 @@ +part of code_builder.src.builders.expression; + +class _AsAssert implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsAssert(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new AssertStatement( + $assert, + $openParen, + _expression.buildExpression(scope), + null, + null, + $closeParen, + null, + ); + } +} diff --git a/lib/src/builders/expression/assign.dart b/lib/src/builders/expression/assign.dart new file mode 100644 index 0000000..168eb73 --- /dev/null +++ b/lib/src/builders/expression/assign.dart @@ -0,0 +1,52 @@ +part of code_builder.src.builders.expression; + +class _AsAssign extends AbstractExpressionMixin { + final ExpressionBuilder _value; + final String _name; + + _AsAssign(this._value, this._name); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new AssignmentExpression( + stringIdentifier(_name), + $equals, + _value.buildExpression(scope), + ); + } +} + +class _AsAssignNew implements StatementBuilder { + final ExpressionBuilder _value; + final String _name; + final TypeBuilder _type; + final Token _modifier; + + _AsAssignNew(this._value, this._name, this._type, this._modifier); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new VariableDeclarationStatement( + new VariableDeclarationList( + null, + null, + _type == null || _modifier != $var ? _modifier : null, + _type?.buildType(scope), + [ + new VariableDeclaration( + stringIdentifier(_name), + $equals, + _value.buildExpression(scope), + ), + ], + ), + $semicolon, + ); + } +} diff --git a/lib/src/builders/expression/invocation.dart b/lib/src/builders/expression/invocation.dart new file mode 100644 index 0000000..b49d4ca --- /dev/null +++ b/lib/src/builders/expression/invocation.dart @@ -0,0 +1,93 @@ +part of code_builder.src.builders.expression; + +/// Builds an invocation AST. +abstract class InvocationBuilder implements ExpressionBuilder { + factory InvocationBuilder._(ExpressionBuilder target) { + return new _FunctionInvocationBuilder(target); + } + + factory InvocationBuilder._on(ExpressionBuilder target, String method) { + return new _MethodInvocationBuilder(target, method); + } + + /// Adds [argument] as a positional argument to this method call. + void addPositionalArgument(ExpressionBuilder argument); + + /// Adds [argument] as a [name]d argument to this method call. + void addNamedArgument(String name, ExpressionBuilder argument); +} + +/// Partial implementation of [InvocationBuilder]. +abstract class AbstractInvocationBuilderMixin implements InvocationBuilder { + final List _positional = []; + final Map _named = {}; + + @override + void addPositionalArgument(ExpressionBuilder argument) { + _positional.add(argument); + } + + @override + void addNamedArgument(String name, ExpressionBuilder argument) { + _named[name] = argument; + } + + /// Returns an [ArgumentList] AST. + ArgumentList buildArgumentList([Scope scope]) { + final allArguments = []; + allArguments.addAll(_positional.map/**/((e) => e.buildExpression(scope))); + _named.forEach((name, e) { + allArguments.add(new NamedExpression( + new Label(stringIdentifier(name), $colon,), + e.buildExpression(scope), + )); + }); + return new ArgumentList( + $openParen, + allArguments, + $closeParen, + ); + } + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); +} + +class _FunctionInvocationBuilder + extends Object + with AbstractInvocationBuilderMixin, AbstractExpressionMixin + implements InvocationBuilder { + final ExpressionBuilder _target; + + _FunctionInvocationBuilder(this._target); + + @override + Expression buildExpression([Scope scope]) { + return new FunctionExpressionInvocation( + _target.buildExpression(scope), + null, + buildArgumentList(scope), + ); + } +} + +class _MethodInvocationBuilder + extends Object + with AbstractInvocationBuilderMixin, AbstractExpressionMixin + implements InvocationBuilder { + final String _method; + final ExpressionBuilder _target; + + _MethodInvocationBuilder(this._target, this._method); + + @override + Expression buildExpression([Scope scope]) { + return new MethodInvocation( + _target.buildExpression(scope), + $period, + stringIdentifier(_method), + null, + buildArgumentList(scope), + ); + } +} diff --git a/lib/src/builders/expression/negate.dart b/lib/src/builders/expression/negate.dart new file mode 100644 index 0000000..1bac44b --- /dev/null +++ b/lib/src/builders/expression/negate.dart @@ -0,0 +1,18 @@ +part of code_builder.src.builders.expression; + +class _NegateExpression extends AbstractExpressionMixin { + final ExpressionBuilder _expression; + + _NegateExpression(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new PrefixExpression( + $not, + _expression.parentheses().buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/expression/operators.dart b/lib/src/builders/expression/operators.dart new file mode 100644 index 0000000..f36140a --- /dev/null +++ b/lib/src/builders/expression/operators.dart @@ -0,0 +1,21 @@ +part of code_builder.src.builders.expression; + +class _AsBinaryExpression extends Object with AbstractExpressionMixin { + final ExpressionBuilder _left; + final ExpressionBuilder _right; + final Token _operator; + + _AsBinaryExpression(this._left, this._right, this._operator); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new BinaryExpression( + _left.buildExpression(scope), + _operator, + _right.buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/expression/return.dart b/lib/src/builders/expression/return.dart new file mode 100644 index 0000000..d57c7de --- /dev/null +++ b/lib/src/builders/expression/return.dart @@ -0,0 +1,19 @@ +part of code_builder.src.builders.expression; + +class _AsReturn implements StatementBuilder { + final ExpressionBuilder _value; + + _AsReturn(this._value); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new ReturnStatement( + $return, + _value.buildExpression(), + $semicolon, + ); + } +} diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart index 870276e..f09c9da 100644 --- a/lib/src/builders/method.dart +++ b/lib/src/builders/method.dart @@ -61,7 +61,7 @@ abstract class MethodBuilder implements HasAnnotations, HasParameters { /// Creates a new [MethodBuilder] that returns `void`. factory MethodBuilder.returnVoid(String name) { - return new _MethodBuilderImpl(name, returns: core.Void); + return new _MethodBuilderImpl(name, returns: core.$void); } /// Returns a [FunctionDeclaration] AST representing the builder. diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index 7b2968b..1447a13 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -4,17 +4,23 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/expression.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/type.dart'; import 'package:code_builder/src/tokens.dart'; +/// An explicit reference to `this`. +final ReferenceBuilder explicitThis = reference('this'); + /// Creates a reference called [name]. ReferenceBuilder reference(String name, [String importUri]) { return new ReferenceBuilder._(name, importUri); } /// An abstract way of representing other types of [AstBuilder]. -class ReferenceBuilder implements AnnotationBuilder, TypeBuilder { +class ReferenceBuilder extends Object + with AbstractExpressionMixin + implements AnnotationBuilder, ExpressionBuilder, TypeBuilder { final String _importFrom; final String _name; @@ -34,6 +40,15 @@ class ReferenceBuilder implements AnnotationBuilder, TypeBuilder { @override AstNode buildAst([Scope scope]) => throw new UnimplementedError(); + @override + Expression buildExpression([Scope scope]) { + return getIdentifier( + scope, + _name, + _importFrom != null ? Uri.parse(_importFrom) : null, + ); + } + @override TypeName buildType([Scope scope]) { return new TypeBuilder(_name, _importFrom).buildType(scope); diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart index bdd8eb6..daa418d 100644 --- a/lib/src/builders/statement.dart +++ b/lib/src/builders/statement.dart @@ -4,36 +4,59 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement/if.dart'; +import 'package:code_builder/src/tokens.dart'; + +export 'package:code_builder/src/builders/statement/if.dart' + show IfStatementBuilder, elseIf, elseThen, ifThen; /// Lazily builds an [Statement] AST when [buildStatement] is invoked. -abstract class StatementBuilder implements AstBuilder { +abstract class StatementBuilder implements AstBuilder, ValidIfStatementMember { /// Returns an [Statement] AST representing the builder. Statement buildStatement([Scope scope]); } /// An [AstBuilder] that can add [StatementBuilder]. abstract class HasStatements implements AstBuilder { + /// Adds [statement] to the builder. + void addStatement(StatementBuilder statement); + + /// Adds [statements] to the builder. + void addStatements(Iterable statements); +} + +/// Implements [HasStatements]. +abstract class HasStatementsMixin extends HasStatements { final List _statements = []; - /// Adds [statement] to the builder. + @override void addStatement(StatementBuilder statement) { _statements.add(statement); } - /// Adds [statements] to the builder. + @override void addStatements(Iterable statements) { _statements.addAll(statements); } -} -/// Implements [HasStatements]. -abstract class HasStatementsMixin extends HasStatements { /// Clones all expressions to [clone]. void cloneStatementsTo(HasStatements clone) { clone.addStatements(_statements); } + /// Returns a [Block] statement. + Block buildBlock([Scope scope]) { + return new Block( + $openCurly, + buildStatements(scope), + $closeCurly, + ); + } + /// Returns a [List] of all built [Statement]s. - List buildStatements([Scope scope]) => - _statements.map/**/((e) => e.buildStatement(scope)).toList(); + List buildStatements([Scope scope]) { + return _statements + .map/**/((e) => e.buildStatement(scope)) + .toList(); + } } diff --git a/lib/src/builders/statement/block.dart b/lib/src/builders/statement/block.dart new file mode 100644 index 0000000..773c2fc --- /dev/null +++ b/lib/src/builders/statement/block.dart @@ -0,0 +1,29 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Represents a series of [StatementBuilder]s as a block statement AST. +abstract class BlockStatementBuilder implements + HasStatements, + StatementBuilder { + /// Creates a new [BlockStatementBuilder]. + factory BlockStatementBuilder() = _BlockStatementBuilder; +} + +class _BlockStatementBuilder + extends Object + with HasStatementsMixin + implements BlockStatementBuilder { + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new Block( + $openCurly, + buildStatements(scope), + $closeCurly, + ); + } +} diff --git a/lib/src/builders/statement/if.dart b/lib/src/builders/statement/if.dart new file mode 100644 index 0000000..d5b87d4 --- /dev/null +++ b/lib/src/builders/statement/if.dart @@ -0,0 +1,94 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:code_builder/src/builders/expression.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/statement/block.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Short-hand syntax for `new IfStatementBuilder(...)`. +IfStatementBuilder ifThen( + ExpressionBuilder condition, [ + Iterable members = const [], +]) { + final ifStmt = new IfStatementBuilder(condition); + IfStatementBuilder current = ifStmt; + for (final member in members) { + if (member is _IfStatementBuilderWrapper) { + if (member._ifStmt != null) { + current.setElse(member._ifStmt); + current = member._ifStmt; + } else { + current.setElse(new BlockStatementBuilder()..addStatements(member._elseStmts)); + current = null; + } + } else if (member is StatementBuilder) { + ifStmt.addStatement(member); + } else { + throw new UnsupportedError('Invalid type: ${member.runtimeType}'); + } + } + return ifStmt; +} + +/// Denotes an [ifStmt] that should be added as an `else if` in [ifThen]. +_IfStatementBuilderWrapper elseIf(IfStatementBuilder ifStmt) { + return new _IfStatementBuilderWrapper(ifStmt, null); +} + +/// Denotes a series of [statements] added to a final `else` in [ifThen]. +_IfStatementBuilderWrapper elseThen(Iterable statements) { + return new _IfStatementBuilderWrapper(null, statements); +} + +class _IfStatementBuilderWrapper implements ValidIfStatementMember { + final IfStatementBuilder _ifStmt; + final Iterable _elseStmts; + + _IfStatementBuilderWrapper(this._ifStmt, this._elseStmts); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within ifThen.'); +} + +/// Marker interface for builders valid for use with [ifThen]. +abstract class ValidIfStatementMember implements AstBuilder {} + +/// Builds an [IfStatement] AST. +abstract class IfStatementBuilder implements HasStatements, StatementBuilder { + /// Returns a new [IfStatementBuilder] where `if (condition) {`. + factory IfStatementBuilder(ExpressionBuilder condition) { + return new _BlockIfStatementBuilder(condition); + } + + /// Adds an `else` block that evaluates [statements]. + void setElse(StatementBuilder statements); +} + +class _BlockIfStatementBuilder extends HasStatementsMixin + implements IfStatementBuilder { + final ExpressionBuilder _condition; + StatementBuilder _elseBlock; + + _BlockIfStatementBuilder(this._condition); + + @override + void setElse(StatementBuilder statements) { + _elseBlock = statements; + } + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new IfStatement( + $if, + $openParen, + _condition.buildExpression(scope), + $closeParen, + buildBlock(scope), + _elseBlock != null ? $else : null, + _elseBlock?.buildStatement(scope), + ); + } +} diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index c6d7c3d..bafbe2b 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -118,9 +118,18 @@ final Token $notEquals = new Token(TokenType.BANG_EQ, 0); /// The '.' token. final Token $period = new Token(TokenType.PERIOD, 0); -/// The '+' token. +/// The `+` token. final Token $plus = new Token(TokenType.PLUS, 0); +/// The `-` token. +final Token $minus = new Token(TokenType.MINUS, 0); + +/// The `*` token. +final Token $multiply = new Token(TokenType.STAR, 0); + +/// The `/` token. +final Token $divide = new Token(TokenType.SLASH, 0); + /// The `return` token. final Token $return = new KeywordToken(Keyword.RETURN, 0); diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart index c60c96a..ad054c8 100644 --- a/test/builders/expression_test.dart +++ b/test/builders/expression_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; import 'package:code_builder/testing.dart'; import 'package:test/test.dart'; @@ -40,4 +41,189 @@ void main() { expect(literal({1: 2, 2: 3}), equalsSource(r'{1 : 2, 2 : 3}')); }); }); + + test('should emit an assert statemnet', () { + expect( + literal(true).asAssert(), + equalsSource(r''' + assert(true); + '''), + ); + }); + + test('should emit an assign expression', () { + expect( + literal(true).asAssign('flag'), + equalsSource(r''' + flag = true + '''), + ); + }); + + test('should emit a const variable assignment statement', () { + expect( + literal(true).asConst('flag'), + equalsSource(r''' + const flag = true; + '''), + ); + }); + + test('should emit a final variable assignment statement', () { + expect( + literal(true).asFinal('flag'), + equalsSource(r''' + final flag = true; + '''), + ); + }); + + test('should emit a simple var assignment statement', () { + expect( + literal(true).asVar('flag'), + equalsSource(r''' + var flag = true; + '''), + ); + }); + + test('should emit a typed assignemnt statement', () { + expect( + literal(true).asVar('flag', core.bool), + equalsSource(r''' + bool flag = true; + '''), + ); + }); + + test('should emit a return statement', () { + expect( + literal(true).asReturn(), + equalsSource(r''' + return true; + '''), + ); + }); + + test('should emit an expression as a statement', () { + expect( + literal(true).asStatement(), + equalsSource(r''' + true; + '''), + ); + }); + + test('should call an expression as a function', () { + expect( + core.identical.call([literal(true), literal(true)]), + equalsSource(r''' + identical(true, true) + '''), + ); + }); + + test('should call an expression with named arguments', () { + expect( + reference('doThing').call( + [literal(true)], + { + 'otherFlag': literal(false), + }, + ), + equalsSource(r''' + doThing(true, otherFlag: false) + '''), + ); + }); + + test('should call a method on an expression', () { + expect( + explicitThis.invoke('doThing', [literal(true)]), + equalsSource(r''' + this.doThing(true) + '''), + ); + }); + + test('should emit an identical() expression', () { + expect( + literal(true).identical(literal(false)), + equalsSource(r''' + identical(true, false) + '''), + ); + }); + + test('should emit an equality (==) expression', () { + expect( + literal(true).equals(literal(false)), + equalsSource(r''' + true == false + '''), + ); + }); + + test('should emit a not equals (!=) expression', () { + expect( + literal(true).notEquals(literal(false)), + equalsSource(r''' + true != false + '''), + ); + }); + + test('should emit a negated expression', () { + expect( + reference('foo').negate(), + equalsSource(r''' + !(foo) + '''), + ); + }); + + test('should add two expressions', () { + expect( + literal(1) + literal(2), + equalsSource(r''' + 1 + 2 + '''), + ); + }); + + test('should subtract two expressions', () { + expect( + literal(2) - literal(1), + equalsSource(r''' + 2 - 1 + '''), + ); + }); + + test('should multiply two expressions', () { + expect( + literal(2) * literal(3), + equalsSource(r''' + 2 * 3 + '''), + ); + }); + + test('should divide two expressions', () { + expect( + literal(3) / literal(2), + equalsSource(r''' + 3 / 2 + '''), + ); + }); + + test('should wrap an expressions in ()', () { + expect( + literal(true).parentheses(), + equalsSource(r''' + (true) + '''), + ); + }); } diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart index 7dec944..a69aae5 100644 --- a/test/builders/method_test.dart +++ b/test/builders/method_test.dart @@ -20,7 +20,7 @@ void main() { test('should emit a top-level void main() function', () { expect( method('main', [ - core.Void, + core.$void, ]), equalsSource(r''' void main(); diff --git a/test/builders/statement_test.dart b/test/builders/statement_test.dart new file mode 100644 index 0000000..0d937c2 --- /dev/null +++ b/test/builders/statement_test.dart @@ -0,0 +1,88 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group('if statements', () { + test('should emit a simple if statement', () { + expect( + ifThen(literal(true), [ + core.print.call([literal('Hello World')]), + ]), + equalsSource(r''' + if (true) { + print('Hello World'); + } + '''), + ); + }); + + test('should emit an if then else statement', () { + expect( + ifThen(literal(true), [ + core.print.call([literal('TRUE')]), + elseThen([ + core.print.call([literal('FALSE')]), + ]), + ]), + equalsSource(r''' + if (true) { + print('TRUE'); + } else { + print('FALSE'); + } + '''), + ); + }); + + test('should emit an if else then statement', () { + final a = reference('a'); + expect( + ifThen(a.equals(literal(1)), [ + core.print.call([literal('Was 1')]), + elseIf(ifThen( + a.equals(literal(2)), [ + core.print.call([literal('Was 2')]), + ] + )), + ]), + equalsSource(r''' + if (a == 1) { + print('Was 1'); + } else if (a == 2) { + print('Was 2'); + } + '''), + ); + }); + + test('should emit an if else if else statement', () { + final a = reference('a'); + expect( + ifThen( + a.equals(literal(1)), [ + core.print.call([literal('Was 1')]), + elseIf(ifThen( + a.equals(literal(2)), [ + core.print.call([literal('Was 2')]), + elseThen([ + core.print.call([literal('Was ') + a]), + ]), + ] + )), + ], + ), + equalsSource(r''' + if (a == 1) { + print('Was 1'); + } else if (a == 2) { + print('Was 2'); + } else { + print('Was ' + a); + } + '''), + ); + }); + }); +} From 858bc0c1c058778d41a4ca3204972cb2c2b3af1a Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 17 Oct 2016 09:00:04 -0700 Subject: [PATCH 12/19] Next large incremental work. --- .gitignore | 18 +- README.md | 3 +- doc/SHORT_LINKS.md | 10 + lib/code_builder.dart | 28 +- lib/dart/async.dart | 41 ++ lib/dart/core.dart | 91 +++-- lib/src/analyzer_patch.dart | 12 +- lib/src/builders/annotation.dart | 10 +- lib/src/builders/class.dart | 106 ++++-- lib/src/builders/expression.dart | 339 ++++++++--------- lib/src/builders/expression/invocation.dart | 63 ++-- lib/src/builders/field.dart | 166 +++++++++ lib/src/builders/method.dart | 390 +++++++++++++++----- lib/src/builders/parameter.dart | 104 +++--- lib/src/builders/reference.dart | 2 +- lib/src/builders/shared.dart | 24 +- lib/src/builders/statement.dart | 33 +- lib/src/builders/statement/block.dart | 8 +- lib/src/builders/statement/if.dart | 59 +-- lib/src/builders/type.dart | 66 +++- lib/src/builders/type/new_instance.dart | 53 +++ lib/src/tokens.dart | 156 ++++---- lib/testing.dart | 64 ++-- test/builders/class_test.dart | 63 +++- test/builders/field_test.dart | 33 ++ test/builders/method_test.dart | 60 ++- test/builders/statement_test.dart | 25 +- test/builders/type_test.dart | 70 ++++ 28 files changed, 1493 insertions(+), 604 deletions(-) create mode 100644 doc/SHORT_LINKS.md create mode 100644 lib/dart/async.dart create mode 100644 lib/src/builders/field.dart create mode 100644 lib/src/builders/type/new_instance.dart create mode 100644 test/builders/field_test.dart create mode 100644 test/builders/type_test.dart diff --git a/.gitignore b/.gitignore index 8d48546..c222564 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# See https://www.dartlang.org/tools/private-files.html - # Files and directories created by pub .buildlog .packages @@ -8,23 +6,9 @@ **/build **/packages -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - # Directory created by dartdoc -doc/api/ +# doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) pubspec.lock - -*.iml -.idea diff --git a/README.md b/README.md index 334a4e0..7b28d6a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # code_builder +[![pub package](https://img.shields.io/pub/v/code_builder.svg)](https://pub.dartlang.org/packages/code_builder) [![Build Status](https://travis-ci.org/dart-lang/code_builder.svg)](https://travis-ci.org/dart-lang/code_builder) -[![Coverage Status](https://coveralls.io/repos/dart-lang/code_builder/badge.svg)](https://coveralls.io/r/dart-lang/code_builder) +[![Coverage Status](https://coveralls.io/repos/github/dart-lang/code_builder/badge.svg?branch=master)](https://coveralls.io/github/dart-lang/code_builder?branch=master) `code_builder` is a fluent Dart API for generating valid Dart source code. diff --git a/doc/SHORT_LINKS.md b/doc/SHORT_LINKS.md new file mode 100644 index 0000000..fc70631 --- /dev/null +++ b/doc/SHORT_LINKS.md @@ -0,0 +1,10 @@ +When using `goo.gl` links in the library, please re-use the following: + +- References to the Dart Library + - `dart:async`: https://goo.gl/Ulqbfz + - `dart:core`: https://goo.gl/XbSfmT + +- References to Github + - CONTRIBUTING.md: https://goo.gl/2LvV7f + - File an issue to update `dart/*.dart`: https://goo.gl/Xc6xAz + \ No newline at end of file diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 54eaec4..6f4b0e7 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -7,13 +7,31 @@ import 'package:meta/meta.dart'; export 'src/builders/annotation.dart' show AnnotationBuilder; export 'src/builders/class.dart' - show clazz, extend, implement, mixin, ClassBuilder; -export 'src/builders/expression.dart' show literal, ExpressionBuilder; -export 'src/builders/method.dart' show constructor, constructorNamed, fieldFormal, method, named, ConstructorBuilder, MethodBuilder; + show asStatic, clazz, extend, implement, mixin, ClassBuilder; +export 'src/builders/expression.dart' + show literal, ExpressionBuilder, InvocationBuilder; +export 'src/builders/field.dart' + show varConst, varField, varFinal, FieldBuilder; +export 'src/builders/method.dart' + show + constructor, + constructorNamed, + getter, + setter, + thisField, + lambda, + method, + named, + ConstructorBuilder, + MethodBuilder, + ValidMethodMember; export 'src/builders/parameter.dart' show parameter, ParameterBuilder; -export 'src/builders/reference.dart' show explicitThis, reference, ReferenceBuilder; +export 'src/builders/reference.dart' + show explicitThis, reference, ReferenceBuilder; export 'src/builders/shared.dart' show AstBuilder, Scope; -export 'src/builders/statement.dart' show ifThen, elseIf, elseThen, IfStatementBuilder, StatementBuilder; +export 'src/builders/statement.dart' + show ifThen, elseIf, elseThen, IfStatementBuilder, StatementBuilder; +export 'src/builders/type.dart' show NewInstanceBuilder, TypeBuilder; final _dartFmt = new DartFormatter(); diff --git a/lib/dart/async.dart b/lib/dart/async.dart new file mode 100644 index 0000000..fcb51b6 --- /dev/null +++ b/lib/dart/async.dart @@ -0,0 +1,41 @@ +// 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. + +/// Contains reference to the `dart:async` SDK for use in code generation. +/// +/// This library is currently *experimental*, and is subject to change; it is +/// currently manually maintained but there might be a strong use case for this +/// to be automatically generated (at least partially) in the near future. +/// +/// ## Usage +/// +/// First import the library: +/// import 'package:code_builder/code_builder.dart'; +/// import 'package:code_builder/dart/async.dart'; +/// +/// All references are _namespaced_ under [async]. Try it: +/// // Outputs: new Future.value('Hello') +/// async.Future.newInstanceNamed('value', [literal('Hello')]); +/// +/// If you are [missing a reference from `dart:async`](https://goo.gl/XbSfmT) +/// please send us a [pull request](https://goo.gl/2LvV7f) or +/// [file an issue](https://goo.gl/IooPfl). +library code_builder.dart.async; + +import 'dart:async' as dart_async; + +import 'package:code_builder/code_builder.dart'; + +/// A namespace for references in `dart:async`. +final DartAsync async = new DartAsync._(); + +/// References to the `dart:async` library for code generation. See [async]. +class DartAsync { + /// References [dart_async.Future]. + final ReferenceBuilder Future = _ref('Future'); + + DartAsync._(); + + static ReferenceBuilder _ref(String name) => reference(name, 'dart:async'); +} diff --git a/lib/dart/core.dart b/lib/dart/core.dart index 37c62de..49349f5 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -1,35 +1,84 @@ +// 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. + +/// Contains reference to the `dart:core` SDK for use in code generation. +/// +/// This library is currently *experimental*, and is subject to change; it is +/// currently manually maintained but there might be a strong use case for this +/// to be automatically generated (at least partially) in the near future. +/// +/// ## Usage +/// +/// First import the library: +/// import 'package:code_builder/code_builder.dart'; +/// import 'package:code_builder/dart/core.dart'; +/// +/// All references are _namespaced_ under [core]. Try it: +/// // Outputs: print('Hello World') +/// core.print.call([literal('Hello World')]); +/// +/// If you are [missing a reference from `dart:core`](https://goo.gl/XbSfmT) +/// please send us a [pull request](https://goo.gl/2LvV7f) or +/// [file an issue](https://goo.gl/IooPfl). +library code_builder.dart.core; + +import 'dart:core' as dart_core; + import 'package:code_builder/code_builder.dart'; -/// Meta programming from `dart:core`. -final core = new _DartCore(); +/// A namespace for references in `dart:core`. +final DartCore core = new DartCore._(); + +/// References to the `dart:core` library for code generation. See [core]. +class DartCore { + /// References [dart_core.bool]. + final ReferenceBuilder bool = _ref('bool'); + + /// References [dart_core.identical]. + final ReferenceBuilder identical = _ref('identical'); -// Forcibly namespaces to avoid conflicts when importing this library. -class _DartCore { - _DartCore(); + /// References [dart_core.deprecated]. + final ReferenceBuilder deprecated = _ref('deprecated'); - /// Reference the core `bool` type. - final bool = reference('bool', 'dart:core'); + /// References [dart_core.override]. + final ReferenceBuilder override = _ref('override'); - /// Reference the core `identical` function. - final identical = reference('identical', 'dart:core'); + /// References [dart_core.print]. + final ReferenceBuilder print = _ref('print'); - /// Reference the core `print` function. - final print = reference('print'); + /// References [dart_core.int]. + final ReferenceBuilder int = _ref('int'); - /// Reference the core `int` type. - final int = reference('int', 'dart:core'); + /// References [dart_core.Deprecated]. + final ReferenceBuilder Deprecated = _ref('Deprecated'); - /// Reference the core `List` type. - final List = reference('List', 'dart:core'); + /// References [dart_core.Iterable]. + final ReferenceBuilder Iterable = _ref('Iterable'); - /// Reference the core `Object` type. - final Object = reference('Object', 'dart:core'); + /// References [dart_core.List]. + final ReferenceBuilder List = _ref('List'); - /// Reference the core `String` type. - final String = reference('String', 'dart:core'); + /// References [dart_core.Map]. + final ReferenceBuilder Map = _ref('Map'); - /// Reference the core `void` type. + /// References [dart_core.Null]. + final ReferenceBuilder Null = _ref('Null'); + + /// References [dart_core.Object]. + final ReferenceBuilder Object = _ref('Object'); + + /// References [dart_core.String]. + final ReferenceBuilder String = _ref('String'); + + /// References `void` type for returning nothing in a method. /// /// **NOTE**: As a language limitation, this cannot be named `void`. - final $void = reference('void'); + final TypeBuilder $void = new TypeBuilder('void'); + + DartCore._(); + + static ReferenceBuilder _ref(dart_core.String name) { + return reference(name, 'dart:core'); + } } diff --git a/lib/src/analyzer_patch.dart b/lib/src/analyzer_patch.dart index 5b27d77..b4a7149 100644 --- a/lib/src/analyzer_patch.dart +++ b/lib/src/analyzer_patch.dart @@ -10,9 +10,6 @@ import 'package:analyzer/src/generated/java_core.dart'; class PrintBuffer implements PrintWriter, StringBuffer { final StringBuffer _impl = new StringBuffer(); - @override - void clear() {} - @override bool get isEmpty => _impl.isEmpty; @@ -22,6 +19,9 @@ class PrintBuffer implements PrintWriter, StringBuffer { @override int get length => _impl.length; + @override + void clear() {} + @override void newLine() { _impl.writeln(); @@ -40,6 +40,9 @@ class PrintBuffer implements PrintWriter, StringBuffer { _impl.writeln(s); } + @override + String toString() => _impl.toString(); + @override void write(Object obj) { _impl.write(obj); @@ -59,7 +62,4 @@ class PrintBuffer implements PrintWriter, StringBuffer { void writeln([Object obj = ""]) { _impl.writeln(obj); } - - @override - String toString() => _impl.toString(); } diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index 8b1b660..b94873b 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -37,13 +37,13 @@ abstract class HasAnnotationsMixin extends HasAnnotations { _annotations.addAll(annotations); } - /// Clones all annotations to [clone]. - void cloneAnnotationsTo(HasAnnotations clone) { - clone.addAnnotations(_annotations); - } - /// Returns a [List] of all built [Annotation]s. List buildAnnotations([Scope scope]) => _annotations .map/**/((a) => a.buildAnnotation(scope)) .toList(); + + /// Clones all annotations to [clone]. + void cloneAnnotationsTo(HasAnnotations clone) { + clone.addAnnotations(_annotations); + } } diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index 1457109..67b1b7e 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -5,6 +5,7 @@ import 'package:analyzer/analyzer.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/method.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/type.dart'; @@ -29,6 +30,17 @@ ClassBuilder clazz( } } else if (member is ConstructorBuilder) { clazz.addConstructor(member); + } else if (member is _StaticFieldWrapper) { + var wrapped = (member as _StaticFieldWrapper)._member; + if (wrapped is MethodBuilder) { + clazz.addMethod(wrapped as MethodBuilder, asStatic: true); + } else { + clazz.addField(wrapped as FieldBuilder, asStatic: true); + } + } else if (member is FieldBuilder) { + clazz.addField(member); + } else if (member is MethodBuilder) { + clazz.addMethod(member as MethodBuilder); } else { throw new StateError('Invalid AST type: ${member.runtimeType}'); } @@ -36,6 +48,21 @@ ClassBuilder clazz( return clazz; } +/// Wrap [member] to be emitted as a `static` method or field. +_StaticFieldWrapper asStatic(ValidClassMember member) { + return new _StaticFieldWrapper(member); +} + +class _StaticFieldWrapper implements ValidClassMember { + final ValidClassMember _member; + + _StaticFieldWrapper(this._member); + + @override + AstNode buildAst([Scope scope]) => + throw new UnsupportedError('Use inside varField'); +} + /// Returns a wrapper around [type] for use with [clazz]. _TypeNameWrapper extend(TypeBuilder type) { return new _TypeNameWrapper( @@ -60,29 +87,9 @@ _TypeNameWrapper mixin(TypeBuilder type) { ); } -class _TypeNameWrapper implements ValidClassMember { - final bool extend; - final bool implement; - final bool mixin; - final TypeBuilder type; - - _TypeNameWrapper( - this.type, { - this.extend: false, - this.implement: false, - this.mixin: false, - }); - - @override - AstNode buildAst([_]) => throw new UnsupportedError('Use within clazz'); -} - /// Lazily builds an [ClassDeclaration] AST when [buildClass] is invoked. abstract class ClassBuilder - implements - AstBuilder, - HasAnnotations, - TypeBuilder { + implements AstBuilder, HasAnnotations, TypeBuilder { /// Returns a new [ClassBuilder] with [name]. factory ClassBuilder( String name, { @@ -92,24 +99,30 @@ abstract class ClassBuilder Iterable asImplements, }) = _ClassBuilderImpl; - /// Returns an [ClassDeclaration] AST representing the builder. - ClassDeclaration buildClass([Scope scope]); - /// Adds a [constructor]. void addConstructor(ConstructorBuilder constructor); + /// Adds a [field]. + void addField(FieldBuilder field, {bool asStatic: false}); + /// Adds an [interface] to implement. void addImplement(TypeBuilder interface); /// Adds [interfaces] to implement. void addImplements(Iterable interfaces); + /// Adds a [method]. + void addMethod(MethodBuilder method, {bool asStatic: false}); + /// Adds a [mixin]. void addMixin(TypeBuilder mixin); /// Adds [mixins]. void addMixins(Iterable mixins); + /// Returns an [ClassDeclaration] AST representing the builder. + ClassDeclaration buildClass([Scope scope]); + /// Sets [extend]. void setExtends(TypeBuilder extend); } @@ -118,9 +131,11 @@ abstract class ClassBuilder abstract class ValidClassMember implements AstBuilder {} class _ClassBuilderImpl extends Object - with HasAnnotationsMixin + with AbstractTypeBuilderMixin, HasAnnotationsMixin implements ClassBuilder { - final _constructors = []; + final _constructors = []; + final _fields = {}; + final _methods = {}; TypeBuilder _extends; final List _with; @@ -146,6 +161,11 @@ class _ClassBuilderImpl extends Object _constructors.add(constructor); } + @override + void addField(FieldBuilder field, {bool asStatic: false}) { + _fields[field] = asStatic; + } + @override void addImplement(TypeBuilder interface) { _implements.add(interface); @@ -156,6 +176,11 @@ class _ClassBuilderImpl extends Object _implements.addAll(interfaces); } + @override + void addMethod(MethodBuilder method, {bool asStatic: false}) { + _methods[method] = asStatic; + } + @override void addMixin(TypeBuilder mixin) { _with.add(mixin); @@ -204,22 +229,45 @@ class _ClassBuilderImpl extends Object null, null, ); + _fields.forEach((field, static) { + clazz.members.add(field.buildField(static, scope)); + }); _constructors.forEach((constructor) { clazz.members.add(constructor.buildConstructor( this, scope, )); }); + _methods.forEach((method, static) { + clazz.members.add(method.buildMethod(static, scope)); + }); return clazz; } + @override + TypeName buildType([Scope scope]) { + return new TypeBuilder(_name).buildType(scope); + } + @override void setExtends(TypeBuilder extend) { _extends = extend; } +} + +class _TypeNameWrapper implements ValidClassMember { + final bool extend; + final bool implement; + final bool mixin; + final TypeBuilder type; + + _TypeNameWrapper( + this.type, { + this.extend: false, + this.implement: false, + this.mixin: false, + }); @override - TypeName buildType([Scope scope]) { - return new TypeBuilder(_name).buildType(scope); - } + AstNode buildAst([_]) => throw new UnsupportedError('Use within clazz'); } diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 2d1da83..815cdb4 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -8,6 +8,7 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/method.dart'; import 'package:code_builder/src/builders/parameter.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/statement.dart'; @@ -22,85 +23,88 @@ part 'expression/negate.dart'; part 'expression/operators.dart'; part 'expression/return.dart'; -/// Lazily builds an [Expression] AST when [buildExpression] is invoked. -abstract class ExpressionBuilder - implements AstBuilder, StatementBuilder, ValidParameterMember { - /// Returns an [Expression] AST representing the builder. - Expression buildExpression([Scope scope]); - - /// Return as a [StatementBuilder] that `assert`s this expression. - StatementBuilder asAssert(); - - /// Returns as a [StatementBuilder] that assigns to an existing [variable]. - StatementBuilder asAssign(String variable); - - /// Returns as a [StatementBuilder] that assigns to a new `const` [variable]. - StatementBuilder asConst(String variable, [TypeBuilder type]); - - /// Returns as a [StatementBuilder] that assigns to a new `final` [variable]. - StatementBuilder asFinal(String variable, [TypeBuilder type]); - - /// Returns as a [StatementBuilder] that assigns to a new `var` [variable]. - /// - /// If [type] is supplied, the resulting statement is `{type} {variable} =`. - StatementBuilder asVar(String variable, [TypeBuilder type]); - - /// Returns as a [StatementBuilder] that builds an `if` statement. - /*If*/ StatementBuilder asIf(); - - /// Returns as a [StatementBuilder] that `return`s this expression. - StatementBuilder asReturn(); - - /// Returns _explicitly_ as a [StatementBuilder]. - /// - /// **NOTE**: [ExpressionBuilder] is _already_ usable as a [StatementBuilder] - /// directly; this API exists in order force [buildAst] to return a - /// [Statement] AST instead of an expression. - StatementBuilder asStatement(); - - /// Returns as an [InvocationBuilder] with arguments added. - InvocationBuilder call( - Iterable positionalArguments, [ - Map namedArguments, - ]); - - /// Returns as an [InvocationBuilder] on [method] of this expression. - InvocationBuilder invoke( - String method, - Iterable positionalArguments, [ - Map namedArguments, - ]); - - /// Returns as an [ExpressionBuilder] comparing using `==` against [other]. - ExpressionBuilder equals(ExpressionBuilder other); +final _false = new BooleanLiteral(new KeywordToken(Keyword.FALSE, 0), true); - /// Returns as an [ExpressionBuilder] comparing using `identical`. - ExpressionBuilder identical(ExpressionBuilder other); +final _null = new NullLiteral(new KeywordToken(Keyword.NULL, 0)); - /// Returns as an [ExpressionBuilder] comparing using `!=` against [other]. - ExpressionBuilder notEquals(ExpressionBuilder other); +final _true = new BooleanLiteral(new KeywordToken(Keyword.TRUE, 0), true); - /// Returns as an [ExpressionBuilder] negating using the `!` operator. - ExpressionBuilder negate(); +/// Returns a pre-defined literal expression of [value]. +/// +/// Only primitive values are allowed. +ExpressionBuilder literal(value) => new _LiteralExpression(_literal(value)); - /// Returns as an [ExpressionBuilder] adding [other]. - ExpressionBuilder operator +(ExpressionBuilder other); +Literal _literal(value) { + if (value == null) { + return _null; + } else if (value is bool) { + return value ? _true : _false; + } else if (value is String) { + return new SimpleStringLiteral(stringToken("'$value'"), value); + } else if (value is int) { + return new IntegerLiteral(stringToken('$value'), value); + } else if (value is double) { + return new DoubleLiteral(stringToken('$value'), value); + } else if (value is List) { + return new ListLiteral( + null, + null, + $openBracket, + value.map/**/(_literal).toList(), + $closeBracket, + ); + } else if (value is Map) { + return new MapLiteral( + null, + null, + $openBracket, + value.keys.map/**/((k) { + return new MapLiteralEntry(_literal(k), $colon, _literal(value[k])); + }).toList(), + $closeBracket, + ); + } + throw new ArgumentError.value(value, 'Unsupported'); +} - /// Returns as an [ExpressionBuilder] subtracting [other]. - ExpressionBuilder operator -(ExpressionBuilder other); +/// Implements much of [ExpressionBuilder]. +abstract class AbstractExpressionMixin implements ExpressionBuilder { + @override + ExpressionBuilder operator *(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $multiply, + ); + } - /// Returns as an [ExpressionBuilder] dividing by [other]. - ExpressionBuilder operator /(ExpressionBuilder other); + @override + ExpressionBuilder operator +(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $plus, + ); + } - /// Returns as an [ExpressionBuilder] multiplying by [other]. - ExpressionBuilder operator *(ExpressionBuilder other); + @override + ExpressionBuilder operator -(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $minus, + ); + } - /// Returns as an [ExpressionBuilder] wrapped in parentheses. - ExpressionBuilder parentheses(); -} + @override + ExpressionBuilder operator /(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $divide, + ); + } -/// Implements much of [ExpressionBuilder]. -abstract class AbstractExpressionMixin implements ExpressionBuilder { @override StatementBuilder asAssert() => new _AsAssert(this); @@ -117,11 +121,6 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { return new _AsAssignNew(this, variable, type, $final); } - @override - StatementBuilder asVar(String variable, [TypeBuilder type]) { - return new _AsAssignNew(this, variable, type, $var); - } - @override IfStatementBuilder asIf() => new IfStatementBuilder(this); @@ -131,6 +130,11 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { @override StatementBuilder asStatement() => new _AsStatement(this); + @override + StatementBuilder asVar(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $var); + } + @override Statement buildStatement([Scope scope]) { return asStatement().buildStatement(scope); @@ -147,21 +151,13 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { return invocation; } - @override - InvocationBuilder invoke( - String method, - Iterable positionalArguments, [ - Map namedArguments = const {}, - ]) { - final invocation = new InvocationBuilder._on(this, method); - positionalArguments.forEach(invocation.addPositionalArgument); - namedArguments.forEach(invocation.addNamedArgument); - return invocation; - } - @override ExpressionBuilder equals(ExpressionBuilder other) { - return new _AsBinaryExpression(this, other, $equalsEquals,); + return new _AsBinaryExpression( + this, + other, + $equalsEquals, + ); } @override @@ -173,55 +169,108 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { } @override - ExpressionBuilder notEquals(ExpressionBuilder other) { - return new _AsBinaryExpression( - this, - other, - $notEquals, - ); + InvocationBuilder invoke( + String method, + Iterable positionalArguments, [ + Map namedArguments = const {}, + ]) { + final invocation = new InvocationBuilder._on(this, method); + positionalArguments.forEach(invocation.addPositionalArgument); + namedArguments.forEach(invocation.addNamedArgument); + return invocation; } @override ExpressionBuilder negate() => new _NegateExpression(this); @override - ExpressionBuilder operator +(ExpressionBuilder other) { + ExpressionBuilder notEquals(ExpressionBuilder other) { return new _AsBinaryExpression( this, other, - $plus, + $notEquals, ); } @override - ExpressionBuilder operator -(ExpressionBuilder other) { - return new _AsBinaryExpression( - this, - other, - $minus, - ); - } + ExpressionBuilder parentheses() => new _ParenthesesExpression(this); +} - @override - ExpressionBuilder operator /(ExpressionBuilder other) { - return new _AsBinaryExpression( - this, - other, - $divide, - ); - } +/// Lazily builds an [Expression] AST when [buildExpression] is invoked. +abstract class ExpressionBuilder + implements AstBuilder, StatementBuilder, ValidParameterMember { + /// Returns as an [ExpressionBuilder] multiplying by [other]. + ExpressionBuilder operator *(ExpressionBuilder other); - @override - ExpressionBuilder operator *(ExpressionBuilder other) { - return new _AsBinaryExpression( - this, - other, - $multiply, - ); - } + /// Returns as an [ExpressionBuilder] adding [other]. + ExpressionBuilder operator +(ExpressionBuilder other); - @override - ExpressionBuilder parentheses() => new _ParenthesesExpression(this); + /// Returns as an [ExpressionBuilder] subtracting [other]. + ExpressionBuilder operator -(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] dividing by [other]. + ExpressionBuilder operator /(ExpressionBuilder other); + + /// Return as a [StatementBuilder] that `assert`s this expression. + StatementBuilder asAssert(); + + /// Returns as a [StatementBuilder] that assigns to an existing [variable]. + StatementBuilder asAssign(String variable); + + /// Returns as a [StatementBuilder] that assigns to a new `const` [variable]. + StatementBuilder asConst(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that assigns to a new `final` [variable]. + StatementBuilder asFinal(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that builds an `if` statement. + /*If*/ StatementBuilder asIf(); + + /// Returns as a [StatementBuilder] that `return`s this expression. + StatementBuilder asReturn(); + + /// Returns _explicitly_ as a [StatementBuilder]. + /// + /// **NOTE**: [ExpressionBuilder] is _already_ usable as a [StatementBuilder] + /// directly; this API exists in order force [buildAst] to return a + /// [Statement] AST instead of an expression. + StatementBuilder asStatement(); + + /// Returns as a [StatementBuilder] that assigns to a new `var` [variable]. + /// + /// If [type] is supplied, the resulting statement is `{type} {variable} =`. + StatementBuilder asVar(String variable, [TypeBuilder type]); + + /// Returns an [Expression] AST representing the builder. + Expression buildExpression([Scope scope]); + + /// Returns as an [InvocationBuilder] with arguments added. + InvocationBuilder call( + Iterable positionalArguments, [ + Map namedArguments, + ]); + + /// Returns as an [ExpressionBuilder] comparing using `==` against [other]. + ExpressionBuilder equals(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] comparing using `identical`. + ExpressionBuilder identical(ExpressionBuilder other); + + /// Returns as an [InvocationBuilder] on [method] of this expression. + InvocationBuilder invoke( + String method, + Iterable positionalArguments, [ + Map namedArguments, + ]); + + /// Returns as an [ExpressionBuilder] negating using the `!` operator. + ExpressionBuilder negate(); + + /// Returns as an [ExpressionBuilder] comparing using `!=` against [other]. + ExpressionBuilder notEquals(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] wrapped in parentheses. + ExpressionBuilder parentheses(); } /// An [AstBuilder] that can add [ExpressionBuilder]. @@ -241,57 +290,15 @@ abstract class HasExpressions implements AstBuilder { /// Implements [HasExpressions]. abstract class HasExpressionsMixin extends HasExpressions { - /// Clones all expressions to [clone]. - void cloneExpressionsTo(HasExpressions clone) { - clone.addExpressions(_expressions); - } - /// Returns a [List] of all built [Expression]s. List buildExpressions([Scope scope]) => _expressions .map/**/((e) => e.buildExpression(scope)) .toList(); -} -final _null = new NullLiteral(new KeywordToken(Keyword.NULL, 0)); -final _true = new BooleanLiteral(new KeywordToken(Keyword.TRUE, 0), true); -final _false = new BooleanLiteral(new KeywordToken(Keyword.FALSE, 0), true); - -/// Returns a pre-defined literal expression of [value]. -/// -/// Only primitive values are allowed. -ExpressionBuilder literal(value) => new _LiteralExpression(_literal(value)); - -Literal _literal(value) { - if (value == null) { - return _null; - } else if (value is bool) { - return value ? _true : _false; - } else if (value is String) { - return new SimpleStringLiteral(stringToken("'$value'"), value); - } else if (value is int) { - return new IntegerLiteral(stringToken('$value'), value); - } else if (value is double) { - return new DoubleLiteral(stringToken('$value'), value); - } else if (value is List) { - return new ListLiteral( - null, - null, - $openBracket, - value.map/**/(_literal).toList(), - $closeBracket, - ); - } else if (value is Map) { - return new MapLiteral( - null, - null, - $openBracket, - value.keys.map/**/((k) { - return new MapLiteralEntry(_literal(k), $colon, _literal(value[k])); - }).toList(), - $closeBracket, - ); + /// Clones all expressions to [clone]. + void cloneExpressionsTo(HasExpressions clone) { + clone.addExpressions(_expressions); } - throw new ArgumentError.value(value, 'Unsupported'); } class _AsStatement implements StatementBuilder { diff --git a/lib/src/builders/expression/invocation.dart b/lib/src/builders/expression/invocation.dart index b49d4ca..1c015e6 100644 --- a/lib/src/builders/expression/invocation.dart +++ b/lib/src/builders/expression/invocation.dart @@ -1,44 +1,31 @@ part of code_builder.src.builders.expression; -/// Builds an invocation AST. -abstract class InvocationBuilder implements ExpressionBuilder { - factory InvocationBuilder._(ExpressionBuilder target) { - return new _FunctionInvocationBuilder(target); - } - - factory InvocationBuilder._on(ExpressionBuilder target, String method) { - return new _MethodInvocationBuilder(target, method); - } - - /// Adds [argument] as a positional argument to this method call. - void addPositionalArgument(ExpressionBuilder argument); - - /// Adds [argument] as a [name]d argument to this method call. - void addNamedArgument(String name, ExpressionBuilder argument); -} - /// Partial implementation of [InvocationBuilder]. abstract class AbstractInvocationBuilderMixin implements InvocationBuilder { - final List _positional = []; - final Map _named = {}; + final List _positional = []; + final Map _named = {}; @override - void addPositionalArgument(ExpressionBuilder argument) { - _positional.add(argument); + void addNamedArgument(String name, ExpressionBuilder argument) { + _named[name] = argument; } @override - void addNamedArgument(String name, ExpressionBuilder argument) { - _named[name] = argument; + void addPositionalArgument(ExpressionBuilder argument) { + _positional.add(argument); } /// Returns an [ArgumentList] AST. ArgumentList buildArgumentList([Scope scope]) { - final allArguments = []; - allArguments.addAll(_positional.map/**/((e) => e.buildExpression(scope))); + final allArguments = []; + allArguments.addAll( + _positional.map/**/((e) => e.buildExpression(scope))); _named.forEach((name, e) { allArguments.add(new NamedExpression( - new Label(stringIdentifier(name), $colon,), + new Label( + stringIdentifier(name), + $colon, + ), e.buildExpression(scope), )); }); @@ -53,8 +40,25 @@ abstract class AbstractInvocationBuilderMixin implements InvocationBuilder { AstNode buildAst([Scope scope]) => buildExpression(scope); } -class _FunctionInvocationBuilder - extends Object +/// Builds an invocation AST. +abstract class InvocationBuilder + implements ExpressionBuilder, StatementBuilder, ValidMethodMember { + factory InvocationBuilder._(ExpressionBuilder target) { + return new _FunctionInvocationBuilder(target); + } + + factory InvocationBuilder._on(ExpressionBuilder target, String method) { + return new _MethodInvocationBuilder(target, method); + } + + /// Adds [argument] as a [name]d argument to this method call. + void addNamedArgument(String name, ExpressionBuilder argument); + + /// Adds [argument] as a positional argument to this method call. + void addPositionalArgument(ExpressionBuilder argument); +} + +class _FunctionInvocationBuilder extends Object with AbstractInvocationBuilderMixin, AbstractExpressionMixin implements InvocationBuilder { final ExpressionBuilder _target; @@ -71,8 +75,7 @@ class _FunctionInvocationBuilder } } -class _MethodInvocationBuilder - extends Object +class _MethodInvocationBuilder extends Object with AbstractInvocationBuilderMixin, AbstractExpressionMixin implements InvocationBuilder { final String _method; diff --git a/lib/src/builders/field.dart b/lib/src/builders/field.dart new file mode 100644 index 0000000..db347b3 --- /dev/null +++ b/lib/src/builders/field.dart @@ -0,0 +1,166 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/expression.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'; + +/// Short-hand for [FieldBuilder]. +FieldBuilder varField( + String name, { + TypeBuilder type, + ExpressionBuilder value, +}) { + return new FieldBuilder( + name, + type: type, + value: value, + ); +} + +/// Short-hand for [FieldBuilder]. +FieldBuilder varFinal( + String name, { + TypeBuilder type, + ExpressionBuilder value, +}) { + return new FieldBuilder.asFinal( + name, + type: type, + value: value, + ); +} + +/// Short-hand for [FieldBuilder]. +FieldBuilder varConst( + String name, { + TypeBuilder type, + ExpressionBuilder value, +}) { + return new FieldBuilder.asConst( + name, + type: type, + value: value, + ); +} + +/// Lazily builds an field AST when builder is invoked. +abstract class FieldBuilder + implements AstBuilder, HasAnnotations, StatementBuilder, ValidClassMember { + /// Creates a new [FieldBuilder] defining a new `var`. + factory FieldBuilder( + String name, { + TypeBuilder type, + ExpressionBuilder value, + }) => + new _FieldBuilderImpl( + name, + keyword: type == null ? Keyword.VAR : null, + type: type, + value: value, + ); + + /// Creates a new [FieldBuilder] defining a new `const`. + factory FieldBuilder.asConst( + String name, { + TypeBuilder type, + ExpressionBuilder value, + }) => + new _FieldBuilderImpl( + name, + keyword: Keyword.CONST, + type: type, + value: value, + ); + + /// Creates a new [FieldBuilder] defining a new `final`. + factory FieldBuilder.asFinal( + String name, { + TypeBuilder type, + ExpressionBuilder value, + }) => + new _FieldBuilderImpl( + name, + keyword: Keyword.FINAL, + type: type, + value: value, + ); + + /// Returns as a [FieldDeclaration] AST. + FieldDeclaration buildField(bool static, [Scope scope]); + + /// Returns as a [TopLevelVariableDeclaration] AST. + TopLevelVariableDeclaration buildTopLevel([Scope scope]); +} + +class _FieldBuilderImpl extends Object + with HasAnnotationsMixin + implements FieldBuilder { + final Keyword _keyword; + final String _name; + final TypeBuilder _type; + final ExpressionBuilder _value; + + _FieldBuilderImpl( + String name, { + Keyword keyword, + TypeBuilder type, + ExpressionBuilder value, + }) + : _keyword = keyword, + _name = name, + _type = type, + _value = value; + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + FieldDeclaration buildField(bool static, [Scope scope]) { + return new FieldDeclaration( + null, + buildAnnotations(scope), + static ? $static : null, + _buildVariableList(scope), + $semicolon, + ); + } + + @override + Statement buildStatement([Scope scope]) { + return new VariableDeclarationStatement( + _buildVariableList(scope), + $semicolon, + ); + } + + @override + TopLevelVariableDeclaration buildTopLevel([Scope scope]) { + return new TopLevelVariableDeclaration( + null, + null, + _buildVariableList(scope), + $semicolon, + ); + } + + VariableDeclarationList _buildVariableList([Scope scope]) { + return new VariableDeclarationList( + null, + null, + _keyword != null ? new KeywordToken(_keyword, 0) : null, + _type?.buildType(scope), + [ + new VariableDeclaration( + stringIdentifier(_name), + _value != null ? $equals : null, + _value?.buildExpression(scope), + ) + ], + ); + } +} diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart index f09c9da..1b902ab 100644 --- a/lib/src/builders/method.dart +++ b/lib/src/builders/method.dart @@ -3,15 +3,60 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; import 'package:code_builder/code_builder.dart'; import 'package:code_builder/dart/core.dart'; import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/class.dart'; import 'package:code_builder/src/builders/parameter.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'; +/// Short-hand for `new ConstructorBuilder(...)`. +ConstructorBuilder constructor( + [Iterable members = const []]) { + return _constructorImpl(members: members); +} + +/// Short-hand for `new ConstructorBuilder(name)`. +ConstructorBuilder constructorNamed(String name, + [Iterable members = const []]) { + return _constructorImpl(name: name, members: members); +} + +/// Short-hand for `new MethodBuilder.getter(...)`. +MethodBuilder getter( + String name, { + Iterable statements, + ExpressionBuilder returns, + TypeBuilder returnType, +}) { + if (returns != null) { + return new MethodBuilder.getter( + name, + returnType: returnType, + returns: returns, + ); + } else { + return new MethodBuilder.getter( + name, + returnType: returnType, + )..addStatements(statements); + } +} + +/// A more short-hand way of constructing a Lambda [MethodBuilder]. +MethodBuilder lambda( + String name, + ExpressionBuilder value, { + TypeBuilder returnType, +}) { + return new MethodBuilder(name, returns: value, returnType: returnType); +} + /// A more short-hand way of constructing a [MethodBuilder]. MethodBuilder method( String name, [ @@ -19,6 +64,7 @@ MethodBuilder method( ]) { final List positional = []; final List<_NamedParameterWrapper> named = <_NamedParameterWrapper>[]; + final List statements = []; TypeBuilder returnType; for (final member in members) { if (member is TypeBuilder) { @@ -27,6 +73,8 @@ MethodBuilder method( positional.add(member); } else if (member is _NamedParameterWrapper) { named.add(member); + } else if (member is StatementBuilder) { + statements.add(member); } else { throw new StateError('Invalid AST type: ${member.runtimeType}'); } @@ -37,6 +85,7 @@ MethodBuilder method( ); positional.forEach(method.addPositional); named.forEach((p) => method.addNamed(p._parameter)); + statements.forEach(method.addStatement); return method; } @@ -45,42 +94,194 @@ _NamedParameterWrapper named(ParameterBuilder parameter) { return new _NamedParameterWrapper(parameter); } -class _NamedParameterWrapper implements ValidConstructorMember, ValidMethodMember { - final ParameterBuilder _parameter; +/// Short-hand for `new MethodBuilder.setter(...)`. +MethodBuilder setter( + String name, + ParameterBuilder value, + Iterable statements, +) { + return new MethodBuilder.setter(name) + ..addPositional(value) + ..addStatements(statements); +} - _NamedParameterWrapper(this._parameter); +/// Returns a wrapper around [parameter] for use with [constructor]. +_FieldParameterWrapper thisField(Object parameter) { + assert(parameter is ParameterBuilder || parameter is _NamedParameterWrapper); + return new _FieldParameterWrapper(parameter); +} + +ConstructorBuilder _constructorImpl({ + Iterable members, + String name, +}) { + final List<_AddParameter> _addFunctions = <_AddParameter>[]; + for (final member in members) { + if (member is ParameterBuilder) { + _addFunctions.add((c) => c.addPositional(member)); + } else if (member is _NamedParameterWrapper) { + _addFunctions.add((c) => c.addNamed(member._parameter)); + } else if (member is _FieldParameterWrapper) { + if (member._parameter is _NamedParameterWrapper) { + _NamedParameterWrapper p = member._parameter; + _addFunctions.add((c) => c.addNamed(p._parameter, asField: true)); + } else if (member._parameter is ParameterBuilder) { + _addFunctions + .add((c) => c.addPositional(member._parameter, asField: true)); + } + } else if (member is StatementBuilder) { + _addFunctions.add((c) => c.addStatement(member)); + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + final constructor = new ConstructorBuilder(name); + _addFunctions.forEach((a) => a(constructor)); + return constructor; +} + +typedef void _AddParameter(ConstructorBuilder constructor); + +/// Lazily builds an [ConstructorBuilder] AST when built. +abstract class ConstructorBuilder + implements + AstBuilder, + HasParameters, + HasStatements, + ValidClassMember { + /// Create a new [ConstructorBuilder], optionally with a [name]. + factory ConstructorBuilder([String name]) = _NormalConstructorBuilder; @override - AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); + void addNamed(ParameterBuilder parameter, {bool asField: false}); + + @override + void addPositional(ParameterBuilder parameter, {bool asField: false}); + + /// Returns an [ConstructorDeclaration] AST representing the builder. + ConstructorDeclaration buildConstructor(TypeBuilder returnType, + [Scope scope]); } /// Lazily builds a method/function AST when the builder is invoked. -abstract class MethodBuilder implements HasAnnotations, HasParameters { +abstract class MethodBuilder + implements HasAnnotations, HasParameters, HasStatements, ValidClassMember { /// Creates a new [MethodBuilder]. - factory MethodBuilder(String name) = _MethodBuilderImpl; + factory MethodBuilder(String name, + {ExpressionBuilder returns, TypeBuilder returnType}) { + if (returns != null) { + return new _LambdaMethodBuilder( + name, + returns, + returnType, + null, + ); + } else { + return new _MethodBuilderImpl( + name, + returns: returnType, + ); + } + } + + /// Creates a getter. + factory MethodBuilder.getter( + String name, { + TypeBuilder returnType, + ExpressionBuilder returns, + }) { + if (returns == null) { + return new _MethodBuilderImpl( + name, + returns: returnType, + property: Keyword.GET, + ); + } else { + return new _LambdaMethodBuilder( + name, + returns, + returnType, + Keyword.GET, + ); + } + } /// Creates a new [MethodBuilder] that returns `void`. - factory MethodBuilder.returnVoid(String name) { - return new _MethodBuilderImpl(name, returns: core.$void); + factory MethodBuilder.returnVoid(String name, {ExpressionBuilder returns}) { + if (returns == null) { + return new _MethodBuilderImpl(name, returns: core.$void); + } + return new _LambdaMethodBuilder( + name, + returns, + core.$void, + null, + ); + } + + /// Creates a setter. + factory MethodBuilder.setter( + String name, { + ExpressionBuilder returns, + }) { + if (returns == null) { + return new _MethodBuilderImpl( + name, + property: Keyword.SET, + ); + } else { + return new _LambdaMethodBuilder( + name, + returns, + null, + Keyword.SET, + ); + } } /// Returns a [FunctionDeclaration] AST representing the builder. FunctionDeclaration buildFunction([Scope scope]); /// Returns an [MethodDeclaration] AST representing the builder. - MethodDeclaration buildMethod([Scope scope]); + MethodDeclaration buildMethod(bool static, [Scope scope]); } +/// A marker interface for an AST that could be added to [ConstructorBuilder]. +abstract class ValidConstructorMember implements ValidMethodMember {} + /// A marker interface for an AST that could be added to [MethodBuilder]. abstract class ValidMethodMember implements AstBuilder {} -class _MethodBuilderImpl extends Object +class _FieldParameterWrapper + implements ValidConstructorMember, ValidMethodMember { + final Object /*ParameterBuilder|_NamedParameterWrapper*/ _parameter; + + _FieldParameterWrapper(this._parameter); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); +} + +class _LambdaMethodBuilder extends Object with HasAnnotationsMixin, HasParametersMixin implements MethodBuilder { + final ExpressionBuilder _expression; final String _name; final TypeBuilder _returnType; + final Keyword _property; + + _LambdaMethodBuilder( + this._name, this._expression, this._returnType, this._property); + + @override + void addStatement(StatementBuilder statement) { + throw new UnsupportedError('Cannot add statement on a Lambda method'); + } - _MethodBuilderImpl(this._name, {TypeBuilder returns}) : _returnType = returns; + @override + void addStatements(Iterable statements) { + throw new UnsupportedError('Cannot add statement on a Lambda method'); + } @override AstNode buildAst([Scope scope]) => buildFunction(scope); @@ -92,113 +293,121 @@ class _MethodBuilderImpl extends Object buildAnnotations(scope), null, _returnType?.buildType(scope), - null, + _property != null ? new KeywordToken(_property, 0) : null, getIdentifier(scope, _name), new FunctionExpression( null, - buildParameterList(scope), - new EmptyFunctionBody($semicolon), + _property != Keyword.GET ? buildParameterList(scope) : null, + new ExpressionFunctionBody( + null, + null, + _expression.buildExpression(scope), + $semicolon, + ), ), ); } @override - MethodDeclaration buildMethod([Scope scope]) { + MethodDeclaration buildMethod(bool static, [Scope scope]) { return new MethodDeclaration( null, buildAnnotations(scope), null, - null, + static ? $static : null, _returnType?.buildType(scope), - null, + _property != null ? new KeywordToken(_property, 0) : null, null, getIdentifier(scope, _name), null, - buildParameterList(scope), - new EmptyFunctionBody($semicolon), + _property != Keyword.GET ? buildParameterList(scope) : null, + new ExpressionFunctionBody( + null, + null, + _expression.buildExpression(scope), + $semicolon, + ), ); } } -/// Returns a wrapper around [parameter] for use with [constructor]. -_FieldParameterWrapper fieldFormal(Object parameter) { - assert(parameter is ParameterBuilder || parameter is _NamedParameterWrapper); - return new _FieldParameterWrapper(parameter); -} - -class _FieldParameterWrapper implements ValidConstructorMember, ValidMethodMember { - final Object /*ParameterBuilder|_NamedParameterWrapper*/ _parameter; +class _MethodBuilderImpl extends Object + with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin + implements MethodBuilder { + final String _name; + final TypeBuilder _returnType; + final Keyword _property; - _FieldParameterWrapper(this._parameter); + _MethodBuilderImpl( + this._name, { + TypeBuilder returns, + Keyword property, + }) + : _returnType = returns, + _property = property; @override - AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); -} - -/// Short-hand for `new ConstructorBuilder(...)`. -ConstructorBuilder constructor([Iterable members = const []]) { - return _constructorImpl(members: members); -} - -/// Short-hand for `new ConstructorBuilder(name)`. -ConstructorBuilder constructorNamed(String name, [Iterable members = const []]) { - return _constructorImpl(name: name, members: members); -} - -typedef void _AddParameter(ConstructorBuilder constructor); + AstNode buildAst([Scope scope]) => buildFunction(scope); -ConstructorBuilder _constructorImpl({ - Iterable members, - String name, -}) { + @override + FunctionDeclaration buildFunction([Scope scope]) { + return new FunctionDeclaration( + null, + buildAnnotations(scope), + null, + _returnType?.buildType(scope), + _property != null ? new KeywordToken(_property, 0) : null, + getIdentifier(scope, _name), + new FunctionExpression( + null, + _property != Keyword.GET ? buildParameterList(scope) : null, + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + buildBlock(scope), + ), + ), + ); + } - final List<_AddParameter> _addFunctions = <_AddParameter> []; - for (final member in members) { - if (member is ParameterBuilder) { - _addFunctions.add((c) => c.addPositional(member)); - } else if (member is _NamedParameterWrapper) { - _addFunctions.add((c) => c.addNamed(member._parameter)); - } else if (member is _FieldParameterWrapper) { - if (member._parameter is _NamedParameterWrapper) { - _NamedParameterWrapper p = member._parameter; - _addFunctions.add((c) => c.addNamed(p._parameter, asField: true)); - } else if (member._parameter is ParameterBuilder) { - _addFunctions.add((c) => c.addPositional(member._parameter, asField: true)); - } - } else { - throw new StateError('Invalid AST type: ${member.runtimeType}'); - } + @override + MethodDeclaration buildMethod(bool static, [Scope scope]) { + return new MethodDeclaration( + null, + buildAnnotations(scope), + null, + static ? $static : null, + _returnType?.buildType(scope), + _property != null ? new KeywordToken(_property, 0) : null, + null, + getIdentifier(scope, _name), + null, + _property != Keyword.GET ? buildParameterList(scope) : null, + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + buildBlock(scope), + ), + ); } - final constructor = new ConstructorBuilder(name); - _addFunctions.forEach((a) => a(constructor)); - return constructor; } -/// A marker interface for an AST that could be added to [ConstructorBuilder]. -abstract class ValidConstructorMember implements ValidMethodMember {} - -/// Lazily builds an [ConstructorBuilder] AST when built. -abstract class ConstructorBuilder - implements - AstBuilder, - HasParameters, - ValidClassMember { - /// Create a new [ConstructorBuilder], optionally with a [name]. - factory ConstructorBuilder([String name]) = _NormalConstructorBuilder; +class _NamedParameterWrapper + implements ValidConstructorMember, ValidMethodMember { + final ParameterBuilder _parameter; - @override - void addNamed(ParameterBuilder parameter, {bool asField: false}); + _NamedParameterWrapper(this._parameter); @override - void addPositional(ParameterBuilder parameter, {bool asField: false}); - - /// Returns an [ConstructorDeclaration] AST representing the builder. - ConstructorDeclaration buildConstructor(TypeBuilder returnType, [Scope scope]); + AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); } -class _NormalConstructorBuilder - extends Object - with HasAnnotationsMixin, HasParametersMixin +class _NormalConstructorBuilder extends Object + with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin implements ConstructorBuilder { final String _name; @@ -210,7 +419,8 @@ class _NormalConstructorBuilder } @override - ConstructorDeclaration buildConstructor(TypeBuilder returnType, [Scope scope]) { + ConstructorDeclaration buildConstructor(TypeBuilder returnType, + [Scope scope]) { return new ConstructorDeclaration( null, buildAnnotations(scope), @@ -224,7 +434,13 @@ class _NormalConstructorBuilder null, null, null, - new EmptyFunctionBody($semicolon), + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + buildBlock(scope), + ), ); } } diff --git a/lib/src/builders/parameter.dart b/lib/src/builders/parameter.dart index ebab326..c37ae27 100644 --- a/lib/src/builders/parameter.dart +++ b/lib/src/builders/parameter.dart @@ -41,28 +41,6 @@ ParameterBuilder parameter( return builder; } -/// A marker interface for an AST that could be added to [ParameterBuilder]. -abstract class ValidParameterMember implements AstBuilder {} - -/// Lazily builds an [FormalParameter] AST the builder is invoked. -abstract class ParameterBuilder - implements AstBuilder, HasAnnotations, ValidConstructorMember, ValidMethodMember { - /// Create a new builder for parameter [name]. - factory ParameterBuilder( - String name, { - TypeBuilder type, - }) = _SimpleParameterBuilder; - - /// Returns as an optional [ParameterBuilder] set to [defaultTo]. - ParameterBuilder asOptional([ExpressionBuilder defaultTo]); - - /// Returns a positional [FormalParameter] AST representing the builder. - FormalParameter buildPositional(bool field, [Scope scope]); - - /// Returns a positional [FormalParameter] AST representing the builder. - FormalParameter buildNamed(bool field, [Scope scope]); -} - /// An [AstBuilder] that can be built up using [ParameterBuilder]. abstract class HasParameters implements AstBuilder { /// Adds [parameter] to the builder. @@ -72,29 +50,9 @@ abstract class HasParameters implements AstBuilder { void addPositional(ParameterBuilder parameter); } -class _ParameterPair { - final bool _isField; - final bool _isNamed; - final ParameterBuilder _parameter; - - _ParameterPair(this._parameter, {bool field: false}) - : _isNamed = false, - _isField = field; - - _ParameterPair.named(this._parameter, {bool field: false}) - : _isNamed = true, - _isField = field; - - FormalParameter buildParameter([Scope scope]) { - return _isNamed - ? _parameter.buildNamed(_isField, scope) - : _parameter.buildPositional(_isField, scope); - } -} - /// Implements [HasParameters]. abstract class HasParametersMixin implements HasParameters { - final List<_ParameterPair> _parameters = <_ParameterPair> []; + final List<_ParameterPair> _parameters = <_ParameterPair>[]; @override void addNamed(ParameterBuilder parameter, {bool asField: false}) { @@ -110,7 +68,9 @@ abstract class HasParametersMixin implements HasParameters { FormalParameterList buildParameterList([Scope scope]) { return new FormalParameterList( $openParen, - _parameters.map/**/((p) => p.buildParameter(scope)).toList(), + _parameters + .map/**/((p) => p.buildParameter(scope)) + .toList(), null, null, $closeParen, @@ -118,6 +78,32 @@ abstract class HasParametersMixin implements HasParameters { } } +/// Lazily builds an [FormalParameter] AST the builder is invoked. +abstract class ParameterBuilder + implements + AstBuilder, + HasAnnotations, + ValidConstructorMember, + ValidMethodMember { + /// Create a new builder for parameter [name]. + factory ParameterBuilder( + String name, { + TypeBuilder type, + }) = _SimpleParameterBuilder; + + /// Returns as an optional [ParameterBuilder] set to [defaultTo]. + ParameterBuilder asOptional([ExpressionBuilder defaultTo]); + + /// Returns a positional [FormalParameter] AST representing the builder. + FormalParameter buildNamed(bool field, [Scope scope]); + + /// Returns a positional [FormalParameter] AST representing the builder. + FormalParameter buildPositional(bool field, [Scope scope]); +} + +/// A marker interface for an AST that could be added to [ParameterBuilder]. +abstract class ValidParameterMember implements AstBuilder {} + class _OptionalParameterBuilder extends Object with HasAnnotationsMixin implements ParameterBuilder { @@ -155,6 +141,26 @@ class _OptionalParameterBuilder extends Object } } +class _ParameterPair { + final bool _isField; + final bool _isNamed; + final ParameterBuilder _parameter; + + _ParameterPair(this._parameter, {bool field: false}) + : _isNamed = false, + _isField = field; + + _ParameterPair.named(this._parameter, {bool field: false}) + : _isNamed = true, + _isField = field; + + FormalParameter buildParameter([Scope scope]) { + return _isNamed + ? _parameter.buildNamed(_isField, scope) + : _parameter.buildPositional(_isField, scope); + } +} + class _SimpleParameterBuilder extends Object with HasAnnotationsMixin implements ParameterBuilder { @@ -176,6 +182,11 @@ class _SimpleParameterBuilder extends Object @override FormalParameter buildAst([Scope scope]) => buildPositional(false, scope); + @override + FormalParameter buildNamed(bool field, [Scope scope]) { + return asOptional().buildNamed(field, scope); + } + @override FormalParameter buildPositional(bool field, [Scope scope]) { if (field) { @@ -199,9 +210,4 @@ class _SimpleParameterBuilder extends Object stringIdentifier(_name), ); } - - @override - FormalParameter buildNamed(bool field, [Scope scope]) { - return asOptional().buildNamed(field, scope); - } } diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index 1447a13..86fe0f0 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -19,7 +19,7 @@ ReferenceBuilder reference(String name, [String importUri]) { /// An abstract way of representing other types of [AstBuilder]. class ReferenceBuilder extends Object - with AbstractExpressionMixin + with AbstractExpressionMixin, AbstractTypeBuilderMixin implements AnnotationBuilder, ExpressionBuilder, TypeBuilder { final String _importFrom; final String _name; diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart index 6d46a59..cb28237 100644 --- a/lib/src/builders/shared.dart +++ b/lib/src/builders/shared.dart @@ -5,6 +5,18 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/tokens.dart'; +/// Returns an [Identifier] for [name] via [scope]. +/// +/// If [scope] is `null`, automatically uses [Scope.identity]. +Identifier getIdentifier(Scope scope, String name, [Uri importFrom]) { + return (scope ?? Scope.identity).getIdentifier(name, importFrom); +} + +/// Returns a string [Literal] from [value]. +Identifier stringIdentifier(String value) => new SimpleIdentifier( + stringToken(value), + ); + /// Lazily builds an analyzer [AstNode] when [buildAst] is invoked. /// /// Most builders should also have specific typed methods for returning their @@ -36,15 +48,3 @@ class _IdentityScope implements Scope { @override Identifier getIdentifier(String name, [_]) => stringIdentifier(name); } - -/// Returns a string [Literal] from [value]. -Identifier stringIdentifier(String value) => new SimpleIdentifier( - stringToken(value), - ); - -/// Returns an [Identifier] for [name] via [scope]. -/// -/// If [scope] is `null`, automatically uses [Scope.identity]. -Identifier getIdentifier(Scope scope, String name, [Uri importFrom]) { - return (scope ?? Scope.identity).getIdentifier(name, importFrom); -} diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart index daa418d..9b17819 100644 --- a/lib/src/builders/statement.dart +++ b/lib/src/builders/statement.dart @@ -3,18 +3,13 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/method.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/statement/if.dart'; import 'package:code_builder/src/tokens.dart'; export 'package:code_builder/src/builders/statement/if.dart' - show IfStatementBuilder, elseIf, elseThen, ifThen; - -/// Lazily builds an [Statement] AST when [buildStatement] is invoked. -abstract class StatementBuilder implements AstBuilder, ValidIfStatementMember { - /// Returns an [Statement] AST representing the builder. - Statement buildStatement([Scope scope]); -} + show IfStatementBuilder, elseIf, elseThen, ifThen; /// An [AstBuilder] that can add [StatementBuilder]. abstract class HasStatements implements AstBuilder { @@ -39,11 +34,6 @@ abstract class HasStatementsMixin extends HasStatements { _statements.addAll(statements); } - /// Clones all expressions to [clone]. - void cloneStatementsTo(HasStatements clone) { - clone.addStatements(_statements); - } - /// Returns a [Block] statement. Block buildBlock([Scope scope]) { return new Block( @@ -59,4 +49,23 @@ abstract class HasStatementsMixin extends HasStatements { .map/**/((e) => e.buildStatement(scope)) .toList(); } + + /// Clones all expressions to [clone]. + void cloneStatementsTo(HasStatements clone) { + clone.addStatements(_statements); + } + + /// Whether at least one statement was added. + bool get hasStatements => _statements.isNotEmpty; +} + +/// Lazily builds an [Statement] AST when [buildStatement] is invoked. +abstract class StatementBuilder + implements + AstBuilder, + ValidIfStatementMember, + ValidConstructorMember, + ValidMethodMember { + /// Returns an [Statement] AST representing the builder. + Statement buildStatement([Scope scope]); } diff --git a/lib/src/builders/statement/block.dart b/lib/src/builders/statement/block.dart index 773c2fc..57ef9eb 100644 --- a/lib/src/builders/statement/block.dart +++ b/lib/src/builders/statement/block.dart @@ -4,15 +4,13 @@ import 'package:code_builder/src/builders/statement.dart'; import 'package:code_builder/src/tokens.dart'; /// Represents a series of [StatementBuilder]s as a block statement AST. -abstract class BlockStatementBuilder implements - HasStatements, - StatementBuilder { +abstract class BlockStatementBuilder + implements HasStatements, StatementBuilder { /// Creates a new [BlockStatementBuilder]. factory BlockStatementBuilder() = _BlockStatementBuilder; } -class _BlockStatementBuilder - extends Object +class _BlockStatementBuilder extends Object with HasStatementsMixin implements BlockStatementBuilder { @override diff --git a/lib/src/builders/statement/if.dart b/lib/src/builders/statement/if.dart index d5b87d4..ad3b9a9 100644 --- a/lib/src/builders/statement/if.dart +++ b/lib/src/builders/statement/if.dart @@ -5,6 +5,16 @@ import 'package:code_builder/src/builders/statement.dart'; import 'package:code_builder/src/builders/statement/block.dart'; import 'package:code_builder/src/tokens.dart'; +/// Denotes an [ifStmt] that should be added as an `else if` in [ifThen]. +_IfStatementBuilderWrapper elseIf(IfStatementBuilder ifStmt) { + return new _IfStatementBuilderWrapper(ifStmt, null); +} + +/// Denotes a series of [statements] added to a final `else` in [ifThen]. +_IfStatementBuilderWrapper elseThen(Iterable statements) { + return new _IfStatementBuilderWrapper(null, statements); +} + /// Short-hand syntax for `new IfStatementBuilder(...)`. IfStatementBuilder ifThen( ExpressionBuilder condition, [ @@ -18,7 +28,8 @@ IfStatementBuilder ifThen( current.setElse(member._ifStmt); current = member._ifStmt; } else { - current.setElse(new BlockStatementBuilder()..addStatements(member._elseStmts)); + current.setElse( + new BlockStatementBuilder()..addStatements(member._elseStmts)); current = null; } } else if (member is StatementBuilder) { @@ -30,29 +41,6 @@ IfStatementBuilder ifThen( return ifStmt; } -/// Denotes an [ifStmt] that should be added as an `else if` in [ifThen]. -_IfStatementBuilderWrapper elseIf(IfStatementBuilder ifStmt) { - return new _IfStatementBuilderWrapper(ifStmt, null); -} - -/// Denotes a series of [statements] added to a final `else` in [ifThen]. -_IfStatementBuilderWrapper elseThen(Iterable statements) { - return new _IfStatementBuilderWrapper(null, statements); -} - -class _IfStatementBuilderWrapper implements ValidIfStatementMember { - final IfStatementBuilder _ifStmt; - final Iterable _elseStmts; - - _IfStatementBuilderWrapper(this._ifStmt, this._elseStmts); - - @override - AstNode buildAst([_]) => throw new UnsupportedError('Use within ifThen.'); -} - -/// Marker interface for builders valid for use with [ifThen]. -abstract class ValidIfStatementMember implements AstBuilder {} - /// Builds an [IfStatement] AST. abstract class IfStatementBuilder implements HasStatements, StatementBuilder { /// Returns a new [IfStatementBuilder] where `if (condition) {`. @@ -64,6 +52,9 @@ abstract class IfStatementBuilder implements HasStatements, StatementBuilder { void setElse(StatementBuilder statements); } +/// Marker interface for builders valid for use with [ifThen]. +abstract class ValidIfStatementMember implements AstBuilder {} + class _BlockIfStatementBuilder extends HasStatementsMixin implements IfStatementBuilder { final ExpressionBuilder _condition; @@ -71,11 +62,6 @@ class _BlockIfStatementBuilder extends HasStatementsMixin _BlockIfStatementBuilder(this._condition); - @override - void setElse(StatementBuilder statements) { - _elseBlock = statements; - } - @override AstNode buildAst([Scope scope]) => buildStatement(scope); @@ -91,4 +77,19 @@ class _BlockIfStatementBuilder extends HasStatementsMixin _elseBlock?.buildStatement(scope), ); } + + @override + void setElse(StatementBuilder statements) { + _elseBlock = statements; + } +} + +class _IfStatementBuilderWrapper implements ValidIfStatementMember { + final IfStatementBuilder _ifStmt; + final Iterable _elseStmts; + + _IfStatementBuilderWrapper(this._ifStmt, this._elseStmts); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within ifThen.'); } diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart index c14e243..87eb7ab 100644 --- a/lib/src/builders/type.dart +++ b/lib/src/builders/type.dart @@ -2,13 +2,77 @@ // 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.builders.type; + import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.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'; import 'package:code_builder/src/builders/parameter.dart'; import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +part 'type/new_instance.dart'; + +/// Implements the `new` and `const` constructor calls. +abstract class AbstractTypeBuilderMixin { + /// Invokes `const` on this type. + NewInstanceBuilder constInstance( + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._const(this); + _addArguments(builder, positional, named); + return builder; + } + + /// Invokes `const` on this type with a [name]d constructor. + NewInstanceBuilder constInstanceWith( + String name, + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._const(this, name); + _addArguments(builder, positional, named); + return builder; + } + + /// Invokes `new` on this type. + NewInstanceBuilder newInstance( + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._new(this); + _addArguments(builder, positional, named); + return builder; + } + + /// Invokes `new` on this type with a [name]d constructor. + NewInstanceBuilder newInstanceWith( + String name, + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._new(this, name); + _addArguments(builder, positional, named); + return builder; + } + + static void _addArguments( + NewInstanceBuilder builder, + Iterable positional, + Map named, + ) { + positional.forEach(builder.addPositionalArgument); + named.forEach(builder.addNamedArgument); + } +} /// Lazily builds an [TypeName] AST when [buildType] is invoked. -class TypeBuilder +class TypeBuilder extends Object + with AbstractTypeBuilderMixin implements AstBuilder, ValidMethodMember, ValidParameterMember { final String _importFrom; final String _name; diff --git a/lib/src/builders/type/new_instance.dart b/lib/src/builders/type/new_instance.dart new file mode 100644 index 0000000..66ceac3 --- /dev/null +++ b/lib/src/builders/type/new_instance.dart @@ -0,0 +1,53 @@ +part of code_builder.src.builders.type; + +/// Lazily builds an [InstanceCreationExpression] AST when built. +/// +/// See [TypeBuilder]: +/// - [TypeBuilder.constInstance] +/// - [TypeBuilder.constInstanceWith] +/// - [TypeBuilder.newInstance] +/// - [TypeBuilder.newInstanceWith] +abstract class NewInstanceBuilder + implements AnnotationBuilder, InvocationBuilder { + factory NewInstanceBuilder._const(TypeBuilder type, [String name]) { + return new _NewInvocationBuilderImpl(Keyword.CONST, type, name); + } + + factory NewInstanceBuilder._new(TypeBuilder type, [String name]) { + return new _NewInvocationBuilderImpl(Keyword.NEW, type, name); + } +} + +class _NewInvocationBuilderImpl extends Object + with AbstractExpressionMixin, AbstractInvocationBuilderMixin + implements NewInstanceBuilder { + final String _name; + final Keyword _keyword; + final TypeBuilder _type; + + _NewInvocationBuilderImpl(this._keyword, this._type, [this._name]); + + @override + Annotation buildAnnotation([Scope scope]) { + return new Annotation( + $at, + _type.buildType(scope).name, + $period, + _name != null ? stringIdentifier(_name) : null, + buildArgumentList(scope), + ); + } + + @override + Expression buildExpression([Scope scope]) { + return new InstanceCreationExpression( + new KeywordToken(_keyword, 0), + new ConstructorName( + _type.buildType(scope), + _name != null ? $period : null, + _name != null ? stringIdentifier(_name) : null, + ), + buildArgumentList(scope), + ); + } +} diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index bafbe2b..082d9ef 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -11,109 +11,100 @@ final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); /// The `assert` token. final Token $assert = new KeywordToken(Keyword.ASSERT, 0); +/// The `@` token. +final Token $at = new Token(TokenType.AT, 0); + /// The `class` token. final Token $class = new KeywordToken(Keyword.CLASS, 0); -/// The `const` token. -final Token $const = new KeywordToken(Keyword.CONST, 0); - -/// The `extends` token. -final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); - -/// The `implements` token. -final Token $implements = new KeywordToken(Keyword.IMPLEMENTS, 0); +/// The `]` token. +final Token $closeBracket = new Token(TokenType.CLOSE_SQUARE_BRACKET, 0); -/// The `with` token. -final Token $with = new KeywordToken(Keyword.WITH, 0); +/// The '}' token. +final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); -/// The `static` token. -final Token $static = new KeywordToken(Keyword.STATIC, 0); +/// The ')' token. +final Token $closeParen = new Token(TokenType.CLOSE_PAREN, 0); -/// The `final` token. -final Token $final = new KeywordToken(Keyword.FINAL, 0); +/// The ':' token. +final Token $colon = new Token(TokenType.COLON, 0); -/// The `var` token. -final Token $var = new KeywordToken(Keyword.VAR, 0); +/// The `const` token. +final Token $const = new KeywordToken(Keyword.CONST, 0); -/// The `this` token. -final Token $this = new KeywordToken(Keyword.THIS, 0); +/// The `/` token. +final Token $divide = new Token(TokenType.SLASH, 0); -/// The `library` token. -final Token $library = new KeywordToken(Keyword.LIBRARY, 0); +/// The `else` token. +final Token $else = new KeywordToken(Keyword.ELSE, 0); -/// The `part` token. -final Token $part = new KeywordToken(Keyword.PART, 0); +/// The '=' token. +final Token $equals = new Token(TokenType.EQ, 0); -/// The `of` token. -final Token $of = new StringToken(TokenType.KEYWORD, 'of', 0); +/// The `==` token. +final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); -/// The `true` token. -final Token $true = new KeywordToken(Keyword.TRUE, 0); +/// The `extends` token. +final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); /// The `false` token. final Token $false = new KeywordToken(Keyword.FALSE, 0); -/// The `null` token. -final Token $null = new KeywordToken(Keyword.NULL, 0); - -/// The `new` token. -final Token $new = new KeywordToken(Keyword.NEW, 0); - -// Simple tokens - -/// The `@` token. -final Token $at = new Token(TokenType.AT, 0); - -/// The '(' token. -final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); - -/// The ')' token. -final Token $closeParen = new Token(TokenType.CLOSE_PAREN, 0); +/// The `final` token. +final Token $final = new KeywordToken(Keyword.FINAL, 0); -/// The '{' token. -final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); +/// The `>` token. +final Token $gt = new Token(TokenType.GT, 0); -/// The '}' token. -final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); +/// The `if` token. +final Token $if = new KeywordToken(Keyword.IF, 0); -/// The '[` token. -final Token $openBracket = new Token(TokenType.OPEN_SQUARE_BRACKET, 0); +// Simple tokens -/// The `]` token. -final Token $closeBracket = new Token(TokenType.CLOSE_SQUARE_BRACKET, 0); +/// The `implements` token. +final Token $implements = new KeywordToken(Keyword.IMPLEMENTS, 0); -/// The `>` token. -final Token $gt = new Token(TokenType.GT, 0); +/// The `library` token. +final Token $library = new KeywordToken(Keyword.LIBRARY, 0); /// The `<` token. final Token $lt = new Token(TokenType.LT, 0); -/// The ':' token. -final Token $colon = new Token(TokenType.COLON, 0); +/// The `-` token. +final Token $minus = new Token(TokenType.MINUS, 0); -/// The `else` token. -final Token $else = new KeywordToken(Keyword.ELSE, 0); +/// The `*` token. +final Token $multiply = new Token(TokenType.STAR, 0); -/// The ';' token. -final Token $semicolon = new Token(TokenType.SEMICOLON, 0); +/// The `new` token. +final Token $new = new KeywordToken(Keyword.NEW, 0); -/// The '=' token. -final Token $equals = new Token(TokenType.EQ, 0); +/// The `!` token. +final Token $not = new Token(TokenType.BANG, 0); -/// The `==` token. -final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); +/// The `!=` token. +final Token $notEquals = new Token(TokenType.BANG_EQ, 0); -/// The `if` token. -final Token $if = new KeywordToken(Keyword.IF, 0); +/// The `null` token. +final Token $null = new KeywordToken(Keyword.NULL, 0); /// The '??=' token. final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); -/// The `!` token. -final Token $not = new Token(TokenType.BANG, 0); +/// The `of` token. +final Token $of = new StringToken(TokenType.KEYWORD, 'of', 0); -/// The `!=` token. -final Token $notEquals = new Token(TokenType.BANG_EQ, 0); +/// The '[` token. +final Token $openBracket = new Token(TokenType.OPEN_SQUARE_BRACKET, 0); + +/// The '{' token. +final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); + +/// The '(' token. +final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); + +/// The `part` token. +final Token $part = new KeywordToken(Keyword.PART, 0); /// The '.' token. final Token $period = new Token(TokenType.PERIOD, 0); @@ -121,20 +112,29 @@ final Token $period = new Token(TokenType.PERIOD, 0); /// The `+` token. final Token $plus = new Token(TokenType.PLUS, 0); -/// The `-` token. -final Token $minus = new Token(TokenType.MINUS, 0); +/// The `return` token. +final Token $return = new KeywordToken(Keyword.RETURN, 0); -/// The `*` token. -final Token $multiply = new Token(TokenType.STAR, 0); +/// The ';' token. +final Token $semicolon = new Token(TokenType.SEMICOLON, 0); -/// The `/` token. -final Token $divide = new Token(TokenType.SLASH, 0); +/// The `static` token. +final Token $static = new KeywordToken(Keyword.STATIC, 0); -/// The `return` token. -final Token $return = new KeywordToken(Keyword.RETURN, 0); +/// The `this` token. +final Token $this = new KeywordToken(Keyword.THIS, 0); -/// Returns a string token for the given string [s]. -StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); +/// The `true` token. +final Token $true = new KeywordToken(Keyword.TRUE, 0); + +/// The `var` token. +final Token $var = new KeywordToken(Keyword.VAR, 0); + +/// The `with` token. +final Token $with = new KeywordToken(Keyword.WITH, 0); /// Returns an int token for the given int [value]. StringToken intToken(int value) => new StringToken(TokenType.INT, '$value', 0); + +/// Returns a string token for the given string [s]. +StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); diff --git a/lib/testing.dart b/lib/testing.dart index 1ea0954..b971938 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -13,47 +13,37 @@ import 'src/pretty_printer.dart'; /// On failure, uses the default string matcher to show a detailed diff between /// the expected and actual source code results. /// -/// **NOTE**: Runs `dartfmt` _and_ prints the source with additional formatting -/// over the default `Ast.toSource` implementation (i.e. adds new lines between -/// methods in classes, and more). +/// **NOTE*:: [source] needs to have `dartfmt` run on it, exact match only. Matcher equalsSource( String source, { Scope scope: Scope.identity, }) { + var canParse = false; try { source = dartfmt(source); + canParse = true; } on FormatterException catch (_) {} return new _EqualsSource( scope, source, - ); -} - -/// Returns a [Matcher] that checks a [CodeBuilder versus [source]. -/// -/// On failure, uses the default string matcher to show a detailed diff between -/// the expected and actual source code results. -/// -/// **NOTE**: Whitespace is ignored. -Matcher equalsUnformatted( - String source, { - Scope scope: Scope.identity, -}) { - return new _EqualsSource( - scope, - source, + canParse, ); } class _EqualsSource extends Matcher { final Scope _scope; final String _source; + final bool _canParse; - _EqualsSource(this._scope, this._source); + _EqualsSource(this._scope, this._source, this._canParse); @override Description describe(Description description) { - return equals(_source).describe(description); + if (_canParse) { + return equals(_source).describe(description); + } else { + return equalsIgnoringWhitespace(_source).describe(description); + } } @override @@ -65,12 +55,21 @@ class _EqualsSource extends Matcher { ) { if (item is AstBuilder) { var origin = _formatAst(item); - return equalsIgnoringWhitespace(_source).describeMismatch( - origin, - mismatchDescription.addDescriptionOf(origin), - matchState, - verbose, - ); + if (_canParse) { + return equals(_source).describeMismatch( + origin, + mismatchDescription.addDescriptionOf(origin), + matchState, + verbose, + ); + } else { + return equalsIgnoringWhitespace(_source).describeMismatch( + origin, + mismatchDescription.addDescriptionOf(origin), + matchState, + verbose, + ); + } } else { return mismatchDescription.add('$item is not a CodeBuilder'); } @@ -79,13 +78,20 @@ class _EqualsSource extends Matcher { @override bool matches(item, _) { if (item is AstBuilder) { - return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); + if (_canParse) { + return equals(_formatAst(item)).matches(_source, {}); + } else { + return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); + } } return false; } String _formatAst(AstBuilder builder) { var astNode = builder.buildAst(_scope); - return prettyToSource(astNode); + if (_canParse) { + return dartfmt(astNode.toSource()); + } + return astNode.toSource(); } } diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart index 8becd15..07e205a 100644 --- a/test/builders/class_test.dart +++ b/test/builders/class_test.dart @@ -4,6 +4,7 @@ import 'package:code_builder/code_builder.dart'; import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/method.dart'; import 'package:code_builder/testing.dart'; import 'package:test/test.dart'; @@ -85,7 +86,7 @@ void main() { clazz('Animal', [ constructor([ parameter('name', [core.String]), - fieldFormal( + thisField( named( parameter('age').asOptional(literal(0)), ), @@ -99,4 +100,64 @@ void main() { '''), ); }); + + test('should emit a class with fields', () { + expect( + clazz('Animal', [ + asStatic( + varField('static1', type: core.String, value: literal('Hello')), + ), + asStatic( + varFinal('static2', type: core.List, value: literal([])), + ), + asStatic( + varConst('static3', type: core.bool, value: literal(true)), + ), + varField('var1', type: core.String, value: literal('Hello')), + varFinal('var2', type: core.List, value: literal([])), + varConst('var3', type: core.bool, value: literal(true)), + ]), + equalsSource(r''' + class Animal { + static String static1 = 'Hello'; + static final List static2 = []; + static const bool static3 = true; + String var1 = 'Hello'; + final List var2 = []; + const bool var3 = true; + } + '''), + ); + }); + + test('should emit a class with methods', () { + expect( + clazz('Animal', [ + asStatic(method('staticMethod', [ + core.$void, + core.print.call([literal('Called staticMethod')]), + ])), + method('instanceMethod', [ + core.$void, + core.print.call([literal('Called instanceMethod')]), + ]), + constructor([ + core.print.call([literal('Called constructor')]), + ]), + ]), + equalsSource(r''' + class Animal { + Animal() { + print('Called constructor'); + } + static void staticMethod() { + print('Called staticMethod'); + } + void instanceMethod() { + print('Called instanceMethod'); + } + } + '''), + ); + }); } diff --git a/test/builders/field_test.dart b/test/builders/field_test.dart new file mode 100644 index 0000000..38511d0 --- /dev/null +++ b/test/builders/field_test.dart @@ -0,0 +1,33 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('emit a var', () { + expect( + varField('a', type: core.String, value: literal('Hello')), + equalsSource(r''' + String a = 'Hello'; + '''), + ); + }); + + test('emit a final', () { + expect( + varFinal('a', type: core.String, value: literal('Hello')), + equalsSource(r''' + final String a = 'Hello'; + '''), + ); + }); + + test('emit a const', () { + expect( + varConst('a', type: core.String, value: literal('Hello')), + equalsSource(r''' + const String a = 'Hello'; + '''), + ); + }); +} diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart index a69aae5..6b12ec8 100644 --- a/test/builders/method_test.dart +++ b/test/builders/method_test.dart @@ -21,8 +21,8 @@ void main() { expect( method('main', [ core.$void, - ]), - equalsSource(r''' + ]).buildMethod(false).toSource(), + equalsIgnoringWhitespace(r''' void main(); '''), ); @@ -32,8 +32,8 @@ void main() { expect( method('main', [ parameter('args', [core.List]), - ]), - equalsSource(r''' + ]).buildMethod(false).toSource(), + equalsIgnoringWhitespace(r''' main(List args); '''), ); @@ -70,8 +70,8 @@ void main() { method('main', [ named(parameter('a')), named(parameter('b').asOptional(literal(true))), - ]), - equalsSource(r''' + ]).buildMethod(false).toSource(), + equalsIgnoringWhitespace(r''' main({a, b : true}); '''), ); @@ -120,5 +120,53 @@ void main() { '''), ); }); + + test('should a method with a lambda value', () { + expect( + lambda('supported', literal(true), returnType: core.bool), + equalsSource(r''' + bool supported() => true; + '''), + ); + }); + }); + + test('should emit a getter with a lambda value', () { + expect( + getter('unsupported', returns: literal(true)), + equalsSource(r''' + get unsupported => true; + '''), + ); + }); + + test('should emit a getter with statements', () { + expect( + getter( + 'values', + returnType: core.Iterable, + statements: [ + literal([]).asReturn(), + ], + ), + equalsSource(r''' + Iterable get values { + return []; + } + '''), + ); + }); + + test('should emit a setter', () { + expect( + setter('name', parameter('name', [core.String]), [ + (reference('name') + literal('!')).asAssign('_name'), + ]), + equalsSource(r''' + set name(String name) { + _name = name + '!'; + } + '''), + ); }); } diff --git a/test/builders/statement_test.dart b/test/builders/statement_test.dart index 0d937c2..f6a0eb4 100644 --- a/test/builders/statement_test.dart +++ b/test/builders/statement_test.dart @@ -41,11 +41,9 @@ void main() { expect( ifThen(a.equals(literal(1)), [ core.print.call([literal('Was 1')]), - elseIf(ifThen( - a.equals(literal(2)), [ - core.print.call([literal('Was 2')]), - ] - )), + elseIf(ifThen(a.equals(literal(2)), [ + core.print.call([literal('Was 2')]), + ])), ]), equalsSource(r''' if (a == 1) { @@ -61,16 +59,15 @@ void main() { final a = reference('a'); expect( ifThen( - a.equals(literal(1)), [ + a.equals(literal(1)), + [ core.print.call([literal('Was 1')]), - elseIf(ifThen( - a.equals(literal(2)), [ - core.print.call([literal('Was 2')]), - elseThen([ - core.print.call([literal('Was ') + a]), - ]), - ] - )), + elseIf(ifThen(a.equals(literal(2)), [ + core.print.call([literal('Was 2')]), + elseThen([ + core.print.call([literal('Was ') + a]), + ]), + ])), ], ), equalsSource(r''' diff --git a/test/builders/type_test.dart b/test/builders/type_test.dart new file mode 100644 index 0000000..f2eaa56 --- /dev/null +++ b/test/builders/type_test.dart @@ -0,0 +1,70 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group('new instance', () { + test('emits a new List', () { + expect( + core.List.newInstance([]), + equalsSource(r''' + new List() + '''), + ); + }); + + test('emits a new List.from', () { + expect( + core.List.newInstanceWith('from', [ + literal([1, 2, 3]), + ]), + equalsSource(r''' + new List.from([1, 2, 3]) + '''), + ); + }); + + test('emits a new const Point', () { + expect( + reference('Point').constInstance([ + literal(1), + literal(2), + ]), + equalsSource(r''' + const Point(1, 2) + '''), + ); + }); + + test('emits a const constructor as an annotation', () { + expect( + clazz('Animal', [ + core.Deprecated.constInstance([literal('Animals are out of style')]), + ]), + equalsSource(r''' + @Deprecated('Animals are out of style') + class Animal {} + '''), + ); + }); + + test('emits a named const constructor as an annotation', () { + expect( + clazz('Animal', [ + reference('Component').constInstanceWith( + 'stateful', + [], + { + 'selector': literal('animal'), + }, + ), + ]), + equalsSource(r''' + @Component.stateful(selector: 'animal') + class Animal {} + '''), + ); + }); + }); +} From 40063964563a2e750547a86480cca74d7ba89ee3 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 18 Oct 2016 17:47:12 -0700 Subject: [PATCH 13/19] Fix docs, more dart core types. --- .gitignore | 2 +- analysis_options.yaml | 4 - lib/code_builder.dart | 1 + lib/dart/core.dart | 138 ++++++++++++++++++++++++++ lib/src/builders/annotation.dart | 2 +- lib/src/builders/class.dart | 6 +- lib/src/builders/statement.dart | 2 +- lib/src/builders/statement/block.dart | 3 +- lib/src/pretty_printer.dart | 4 +- lib/testing.dart | 2 - 10 files changed, 148 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index c222564..ae6cc48 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ **/packages # Directory created by dartdoc -# doc/api/ +doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) diff --git a/analysis_options.yaml b/analysis_options.yaml index b8c9a7b..95ee488 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -15,7 +15,6 @@ linter: - valid_regexps - always_declare_return_types - annotate_overrides - # - avoid_as - avoid_init_to_null - avoid_return_types_on_setters - await_only_futures @@ -25,7 +24,6 @@ linter: - empty_constructor_bodies - library_names - library_prefixes - # - non_constant_identifier_names - only_throw_errors - overridden_fields - package_api_docs @@ -33,8 +31,6 @@ linter: - prefer_is_not_empty - public_member_api_docs - slash_for_doc_comments - - sort_constructors_first - - sort_unnamed_constructors_first - type_init_formals - unnecessary_brace_in_string_interp - unnecessary_getters_setters diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 6f4b0e7..6bacd00 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -26,6 +26,7 @@ export 'src/builders/method.dart' MethodBuilder, ValidMethodMember; export 'src/builders/parameter.dart' show parameter, ParameterBuilder; +export 'src/pretty_printer.dart' show prettyToSource; export 'src/builders/reference.dart' show explicitThis, reference, ReferenceBuilder; export 'src/builders/shared.dart' show AstBuilder, Scope; diff --git a/lib/dart/core.dart b/lib/dart/core.dart index 49349f5..3289bda 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -47,30 +47,168 @@ class DartCore { /// References [dart_core.print]. final ReferenceBuilder print = _ref('print'); + /// References [dart_core.identityHashCode]. + final ReferenceBuilder identityHashCode = _ref('identityHashCode'); + /// References [dart_core.int]. final ReferenceBuilder int = _ref('int'); + /// References [dart_core.AbstractClassInstantiationError]. + final ReferenceBuilder AbstractClassInstantiationError = _ref('AbstractClassInstantiationError'); + + /// References [dart_core.ArgumentError]. + final ReferenceBuilder ArgumentError = _ref('ArgumentError'); + + /// References [dart_core.AssertionError]. + final ReferenceBuilder AssertionError = _ref('AssertionError'); + + /// References [dart_core.BidirectionalIterator]. + final ReferenceBuilder BidirectionalIterator = _ref('BidirectionalIterator'); + + /// References [dart_core.CastError]. + final ReferenceBuilder CastError = _ref('CastError'); + + /// References [dart_core.Comparable]. + final ReferenceBuilder Comparable = _ref('Comparable'); + + /// References [dart_core.Comparator] + final ReferenceBuilder Comparator = _ref('Comparator'); + + /// References [dart_core.ConcurrentModificationError]. + final ReferenceBuilder ConcurrentModificationError = _ref('ConcurrentModificationError'); + + /// References [dart_core.CyclicInitializationError]. + final ReferenceBuilder CyclicInitializationError = _ref('CyclicInitializationError'); + + /// References [dart_core.DateTime]. + final ReferenceBuilder DateTime = _ref('DateTime'); + /// References [dart_core.Deprecated]. final ReferenceBuilder Deprecated = _ref('Deprecated'); + /// References [dart_core.Duration]. + final ReferenceBuilder Duration = _ref('Duration'); + + /// References [dart_core.Error]. + final ReferenceBuilder Error = _ref('Error'); + + /// References [dart_core.Exception]. + final ReferenceBuilder Exception = _ref('Exception'); + + /// References [dart_core.Expando]. + final ReferenceBuilder Expando = _ref('Expando'); + + /// References [dart_core.FallThroughError]. + final ReferenceBuilder FallThroughError = _ref('FallThroughError'); + + /// References [dart_core.FormatException]. + final ReferenceBuilder FormatException = _ref('FormatException'); + + /// References [dart_core.Function]. + final ReferenceBuilder Function = _ref('Function'); + + /// References [dart_core.IndexError]. + final ReferenceBuilder IndexError = _ref('IndexError'); + + /// References [dart_core.IntegerDivisionByZeroException]. + final ReferenceBuilder IntegerDivisionByZeroException = _ref('IntegerDivisionByZeroException'); + + /// References [dart_core.Invocation]. + final ReferenceBuilder Invocation = _ref('Invocation'); + /// References [dart_core.Iterable]. final ReferenceBuilder Iterable = _ref('Iterable'); + /// References [dart_core.Iterator]. + final ReferenceBuilder Iterator = _ref('Iterator'); + /// References [dart_core.List]. final ReferenceBuilder List = _ref('List'); /// References [dart_core.Map]. final ReferenceBuilder Map = _ref('Map'); + /// References [dart_core.Match]. + final ReferenceBuilder Match = _ref('Match'); + + /// References [dart_core.NoSuchMethodError]. + final ReferenceBuilder NoSuchMethodError = _ref('NoSuchMethodError'); + /// References [dart_core.Null]. final ReferenceBuilder Null = _ref('Null'); + /// References [dart_core.NullThrownError]. + final ReferenceBuilder NullThrownError = _ref('NullThrownError'); + /// References [dart_core.Object]. final ReferenceBuilder Object = _ref('Object'); + /// References [dart_core.OutOfMemoryError]. + final ReferenceBuilder OutOfMemoryError = _ref('OutOfMemoryError'); + + /// References [dart_core.Pattern]. + final ReferenceBuilder Pattern = _ref('Pattern'); + + /// References [dart_core.RangeError]. + final ReferenceBuilder RangeError = _ref('RangeError'); + + /// References [dart_core.RegExp]. + final ReferenceBuilder RegExp = _ref('RegExp'); + + /// References [dart_core.RuneIterator]. + final ReferenceBuilder RuneIterator = _ref('RuneIterator'); + + /// References [dart_core.Runes]. + final ReferenceBuilder Runes = _ref('Runes'); + + /// References [dart_core.Set]. + final ReferenceBuilder Set = _ref('Set'); + + /// References [dart_core.Sink]. + final ReferenceBuilder Sink = _ref('Sink'); + + /// References [dart_core.StackOverflowError]. + final ReferenceBuilder StackOverflowError = _ref('StackOverflowError'); + + /// References [dart_core.StackTrace]. + final ReferenceBuilder StackTrace = _ref('StackTrace'); + + /// References [dart_core.StateError]. + final ReferenceBuilder StateError = _ref('StateError'); + + /// References [dart_core.Stopwatch]. + final ReferenceBuilder Stopwatch = _ref('Stopwatch'); + /// References [dart_core.String]. final ReferenceBuilder String = _ref('String'); + /// References [dart_core.StringBuffer]. + final ReferenceBuilder StringBuffer = _ref('StringBuffer'); + + /// References [dart_core.StringSink]. + final ReferenceBuilder StringSink = _ref('StringSink'); + + /// References [dart_core.Symbol]. + final ReferenceBuilder Symbol = _ref('Symbol'); + + /// References [dart_core.Type]. + final ReferenceBuilder Type = _ref('Type'); + + /// References [dart_core.TypeError]. + final ReferenceBuilder TypeError = _ref('TypeError'); + + /// References [dart_core.UnimplementedError]. + final ReferenceBuilder UnimplementedError = _ref('UnimplementedError'); + + /// References [dart_core.UnsupportedError]. + final ReferenceBuilder UnsupportedError = _ref('UnsupportedError'); + + /// References [dart_core.Uri]. + final ReferenceBuilder Uri = _ref('Uri'); + + /// References [dart_core.UriData]. + final ReferenceBuilder UriData = _ref('UriData'); + /// References `void` type for returning nothing in a method. /// /// **NOTE**: As a language limitation, this cannot be named `void`. diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index b94873b..eb68c15 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -24,7 +24,7 @@ abstract class HasAnnotations implements AstBuilder { } /// Implements [HasAnnotations]. -abstract class HasAnnotationsMixin extends HasAnnotations { +abstract class HasAnnotationsMixin implements HasAnnotations { final List _annotations = []; @override diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index 67b1b7e..5a035a5 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -31,16 +31,16 @@ ClassBuilder clazz( } else if (member is ConstructorBuilder) { clazz.addConstructor(member); } else if (member is _StaticFieldWrapper) { - var wrapped = (member as _StaticFieldWrapper)._member; + var wrapped = member._member; if (wrapped is MethodBuilder) { - clazz.addMethod(wrapped as MethodBuilder, asStatic: true); + clazz.addMethod(wrapped, asStatic: true); } else { clazz.addField(wrapped as FieldBuilder, asStatic: true); } } else if (member is FieldBuilder) { clazz.addField(member); } else if (member is MethodBuilder) { - clazz.addMethod(member as MethodBuilder); + clazz.addMethod(member); } else { throw new StateError('Invalid AST type: ${member.runtimeType}'); } diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart index 9b17819..d12b048 100644 --- a/lib/src/builders/statement.dart +++ b/lib/src/builders/statement.dart @@ -21,7 +21,7 @@ abstract class HasStatements implements AstBuilder { } /// Implements [HasStatements]. -abstract class HasStatementsMixin extends HasStatements { +abstract class HasStatementsMixin implements HasStatements { final List _statements = []; @override diff --git a/lib/src/builders/statement/block.dart b/lib/src/builders/statement/block.dart index 57ef9eb..f3c55b5 100644 --- a/lib/src/builders/statement/block.dart +++ b/lib/src/builders/statement/block.dart @@ -10,7 +10,8 @@ abstract class BlockStatementBuilder factory BlockStatementBuilder() = _BlockStatementBuilder; } -class _BlockStatementBuilder extends Object +class _BlockStatementBuilder + extends Object with HasStatementsMixin implements BlockStatementBuilder { @override diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart index 2a077ee..9bb1d08 100644 --- a/lib/src/pretty_printer.dart +++ b/lib/src/pretty_printer.dart @@ -27,10 +27,8 @@ String prettyToSource(AstNode astNode) { } } -// TODO(matanl): Remove copied-pasted methods when API becomes available. -// https://github.com/dart-lang/sdk/issues/27169 +// https://github.com/dart-lang/code_builder/issues/16 class _PrettyToSourceVisitor extends ToSourceVisitor { - // https://github.com/dart-lang/sdk/issues/27301 final StringBuffer _buffer; _PrettyToSourceVisitor(PrintBuffer buffer) diff --git a/lib/testing.dart b/lib/testing.dart index b971938..dfba4b9 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -6,8 +6,6 @@ import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'package:matcher/matcher.dart'; -import 'src/pretty_printer.dart'; - /// Returns a [Matcher] that checks an [AstBuilder] versus [source]. /// /// On failure, uses the default string matcher to show a detailed diff between From e3e7f35c4deb88de21e928f05d38384c1f8d72ca Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Wed, 19 Oct 2016 16:27:26 -0700 Subject: [PATCH 14/19] Add scoping back. --- lib/dart/core.dart | 12 ++++++++---- lib/src/builders/statement/block.dart | 3 +-- lib/src/tokens.dart | 3 +++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/dart/core.dart b/lib/dart/core.dart index 3289bda..21e97b3 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -54,7 +54,8 @@ class DartCore { final ReferenceBuilder int = _ref('int'); /// References [dart_core.AbstractClassInstantiationError]. - final ReferenceBuilder AbstractClassInstantiationError = _ref('AbstractClassInstantiationError'); + final ReferenceBuilder AbstractClassInstantiationError = + _ref('AbstractClassInstantiationError'); /// References [dart_core.ArgumentError]. final ReferenceBuilder ArgumentError = _ref('ArgumentError'); @@ -75,10 +76,12 @@ class DartCore { final ReferenceBuilder Comparator = _ref('Comparator'); /// References [dart_core.ConcurrentModificationError]. - final ReferenceBuilder ConcurrentModificationError = _ref('ConcurrentModificationError'); + final ReferenceBuilder ConcurrentModificationError = + _ref('ConcurrentModificationError'); /// References [dart_core.CyclicInitializationError]. - final ReferenceBuilder CyclicInitializationError = _ref('CyclicInitializationError'); + final ReferenceBuilder CyclicInitializationError = + _ref('CyclicInitializationError'); /// References [dart_core.DateTime]. final ReferenceBuilder DateTime = _ref('DateTime'); @@ -111,7 +114,8 @@ class DartCore { final ReferenceBuilder IndexError = _ref('IndexError'); /// References [dart_core.IntegerDivisionByZeroException]. - final ReferenceBuilder IntegerDivisionByZeroException = _ref('IntegerDivisionByZeroException'); + final ReferenceBuilder IntegerDivisionByZeroException = + _ref('IntegerDivisionByZeroException'); /// References [dart_core.Invocation]. final ReferenceBuilder Invocation = _ref('Invocation'); diff --git a/lib/src/builders/statement/block.dart b/lib/src/builders/statement/block.dart index f3c55b5..57ef9eb 100644 --- a/lib/src/builders/statement/block.dart +++ b/lib/src/builders/statement/block.dart @@ -10,8 +10,7 @@ abstract class BlockStatementBuilder factory BlockStatementBuilder() = _BlockStatementBuilder; } -class _BlockStatementBuilder - extends Object +class _BlockStatementBuilder extends Object with HasStatementsMixin implements BlockStatementBuilder { @override diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index 082d9ef..17e70b5 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -8,6 +8,9 @@ import 'package:analyzer/src/dart/ast/token.dart'; /// The `abstract` token. final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); +/// The `as` token. +final Token $as = new KeywordToken(Keyword.AS, 0); + /// The `assert` token. final Token $assert = new KeywordToken(Keyword.ASSERT, 0); From 92b50f76671c4c055d49e3ae64cb18672d8d6be9 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 20 Oct 2016 11:27:38 -0700 Subject: [PATCH 15/19] Add e2e test. --- analysis_options.yaml | 4 - lib/code_builder.dart | 1 + lib/src/builders/field.dart | 8 +- lib/src/builders/file.dart | 156 ++++++++++++++++++++++++++++++++ lib/src/builders/method.dart | 8 +- lib/src/builders/reference.dart | 4 +- lib/src/builders/shared.dart | 31 +------ lib/src/builders/type.dart | 4 +- lib/src/scope.dart | 116 ++++++++++++++++++++++++ lib/testing.dart | 10 +- test/builders/shared_test.dart | 4 - test/e2e_test.dart | 108 ++++++++++++++++++++++ test/scope_test.dart | 84 +++++++++++++++++ 13 files changed, 490 insertions(+), 48 deletions(-) create mode 100644 lib/src/builders/file.dart create mode 100644 lib/src/scope.dart create mode 100644 test/e2e_test.dart create mode 100644 test/scope_test.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 95ee488..b256a69 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -26,12 +26,8 @@ linter: - library_prefixes - only_throw_errors - overridden_fields - - package_api_docs - package_prefixed_library_names - prefer_is_not_empty - - public_member_api_docs - slash_for_doc_comments - type_init_formals - - unnecessary_brace_in_string_interp - unnecessary_getters_setters - - package_names diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 6bacd00..4506474 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -12,6 +12,7 @@ export 'src/builders/expression.dart' show literal, ExpressionBuilder, InvocationBuilder; export 'src/builders/field.dart' show varConst, varField, varFinal, FieldBuilder; +export 'src/builders/file.dart' show ImportBuilder, LibraryBuilder, PartBuilder; export 'src/builders/method.dart' show constructor, diff --git a/lib/src/builders/field.dart b/lib/src/builders/field.dart index db347b3..0286646 100644 --- a/lib/src/builders/field.dart +++ b/lib/src/builders/field.dart @@ -50,7 +50,11 @@ FieldBuilder varConst( /// Lazily builds an field AST when builder is invoked. abstract class FieldBuilder - implements AstBuilder, HasAnnotations, StatementBuilder, ValidClassMember { + implements + AstBuilder, + HasAnnotations, + StatementBuilder, + ValidClassMember { /// Creates a new [FieldBuilder] defining a new `var`. factory FieldBuilder( String name, { @@ -117,7 +121,7 @@ class _FieldBuilderImpl extends Object _value = value; @override - AstNode buildAst([Scope scope]) => buildStatement(scope); + TopLevelVariableDeclaration buildAst([Scope scope]) => buildTopLevel(scope); @override FieldDeclaration buildField(bool static, [Scope scope]) { diff --git a/lib/src/builders/file.dart b/lib/src/builders/file.dart new file mode 100644 index 0000000..dd82969 --- /dev/null +++ b/lib/src/builders/file.dart @@ -0,0 +1,156 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Lazily builds a file of Dart source code. +/// +/// See [LibraryBuilder] and [PartBuilder] for concrete implementations. +abstract class FileBuilder implements AstBuilder { + final List> _members = + >[]; + + FileBuilder._(); + + /// Adds a top-level field or class [member]. + void addMember(AstBuilder member) { + _members.add(member); + } + + /// Adds top-level field or class [members]. + void addMembers(Iterable> members) { + _members.addAll(members); + } +} + +/// Lazily builds a standalone file (library) of Dart source code. +class LibraryBuilder extends FileBuilder { + final List> _directives = >[]; + final Scope _scope; + + /// Creates a new standalone Dart library, optionally with [name]. + factory LibraryBuilder([String name]) => + new LibraryBuilder._(name, Scope.identity); + + /// Creates a new standalone Dart library, optionally with [name]. + /// + /// Uses the default [Scope] implementation unless [scope] is set. + factory LibraryBuilder.scope({String name, Scope scope}) { + return new LibraryBuilder._(name, scope ?? new Scope()); + } + + LibraryBuilder._(String name, this._scope) : super._() { + if (name != null) { + _directives.add(new _LibraryDirectiveBuilder(name)); + } + } + + /// Adds a file [directive]. + void addDirective(AstBuilder directive) { + _directives.add(directive); + } + + /// Add file [directives]. + void addDirectives(Iterable> directives) { + _directives.addAll(directives); + } + + @override + CompilationUnit buildAst([_]) { + var members = _members.map((m) => m.buildAst(_scope)).toList(); + var directives = [] + ..addAll(_scope.toImports().map((d) => d.buildAst())) + ..addAll(_directives.map((d) => d.buildAst())); + return new CompilationUnit( + null, + null, + directives, + members, + null, + ); + } +} + +/// Lazily builds a partial file (part of) Dart source code. +class PartBuilder extends FileBuilder { + final String _name; + + /// Creates a partial Dart file. + factory PartBuilder(String name) = PartBuilder._; + + PartBuilder._(this._name) : super._(); + + @override + CompilationUnit buildAst([_]) { + return new CompilationUnit( + null, + null, + [ + new PartOfDirective( + null, + null, + $part, + $of, + new LibraryIdentifier([ + new SimpleIdentifier(stringToken(_name)), + ]), + $semicolon, + ) + ], + _members.map((m) => m.buildAst()).toList(), + null, + ); + } +} + +class _LibraryDirectiveBuilder implements AstBuilder { + final String _name; + + _LibraryDirectiveBuilder(this._name); + + @override + LibraryDirective buildAst([_]) { + return new LibraryDirective( + null, + null, + $library, + new LibraryIdentifier([ + new SimpleIdentifier( + stringToken(_name), + ), + ]), + $semicolon, + ); + } +} + +/// Lazily builds an [ImportDirective] AST when built. +class ImportBuilder implements AstBuilder { + final String _prefix; + final String _uri; + + factory ImportBuilder(String path, {String prefix}) { + return new ImportBuilder._(path, prefix); + } + + ImportBuilder._(this._uri, this._prefix); + + @override + ImportDirective buildAst([_]) { + return new ImportDirective( + null, + null, + null, + new SimpleStringLiteral(stringToken("'$_uri'"), _uri), + null, + null, + _prefix != null ? $as : null, + _prefix != null ? stringIdentifier(_prefix) : null, + null, + $semicolon, + ); + } +} diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart index 1b902ab..4461486 100644 --- a/lib/src/builders/method.dart +++ b/lib/src/builders/method.dart @@ -294,7 +294,7 @@ class _LambdaMethodBuilder extends Object null, _returnType?.buildType(scope), _property != null ? new KeywordToken(_property, 0) : null, - getIdentifier(scope, _name), + identifier(scope, _name), new FunctionExpression( null, _property != Keyword.GET ? buildParameterList(scope) : null, @@ -318,7 +318,7 @@ class _LambdaMethodBuilder extends Object _returnType?.buildType(scope), _property != null ? new KeywordToken(_property, 0) : null, null, - getIdentifier(scope, _name), + stringIdentifier(_name), null, _property != Keyword.GET ? buildParameterList(scope) : null, new ExpressionFunctionBody( @@ -357,7 +357,7 @@ class _MethodBuilderImpl extends Object null, _returnType?.buildType(scope), _property != null ? new KeywordToken(_property, 0) : null, - getIdentifier(scope, _name), + identifier(scope, _name), new FunctionExpression( null, _property != Keyword.GET ? buildParameterList(scope) : null, @@ -382,7 +382,7 @@ class _MethodBuilderImpl extends Object _returnType?.buildType(scope), _property != null ? new KeywordToken(_property, 0) : null, null, - getIdentifier(scope, _name), + identifier(scope, _name), null, _property != Keyword.GET ? buildParameterList(scope) : null, !hasStatements diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index 86fe0f0..31be042 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -42,10 +42,10 @@ class ReferenceBuilder extends Object @override Expression buildExpression([Scope scope]) { - return getIdentifier( + return identifier( scope, _name, - _importFrom != null ? Uri.parse(_importFrom) : null, + _importFrom, ); } diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart index cb28237..3c16264 100644 --- a/lib/src/builders/shared.dart +++ b/lib/src/builders/shared.dart @@ -3,19 +3,14 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/scope.dart'; import 'package:code_builder/src/tokens.dart'; -/// Returns an [Identifier] for [name] via [scope]. -/// -/// If [scope] is `null`, automatically uses [Scope.identity]. -Identifier getIdentifier(Scope scope, String name, [Uri importFrom]) { - return (scope ?? Scope.identity).getIdentifier(name, importFrom); -} +export 'package:code_builder/src/scope.dart'; /// Returns a string [Literal] from [value]. -Identifier stringIdentifier(String value) => new SimpleIdentifier( - stringToken(value), - ); +Identifier stringIdentifier(String value) => + new SimpleIdentifier(stringToken(value)); /// Lazily builds an analyzer [AstNode] when [buildAst] is invoked. /// @@ -30,21 +25,3 @@ abstract class AstBuilder { /// have conflicting or missing imports. T buildAst([Scope scope]); } - -/// Maintains a scope to prevent conflicting or missing imports in a file. -abstract class Scope { - /// A [Scope] that does not apply any prefixing. - static const Scope identity = const _IdentityScope(); - - /// Returns an [Identifier] for [name]. - /// - /// Optionally, specify the [importFrom] URI. - Identifier getIdentifier(String name, [Uri importFrom]); -} - -class _IdentityScope implements Scope { - const _IdentityScope(); - - @override - Identifier getIdentifier(String name, [_]) => stringIdentifier(name); -} diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart index 87eb7ab..34d21ba 100644 --- a/lib/src/builders/type.dart +++ b/lib/src/builders/type.dart @@ -88,10 +88,10 @@ class TypeBuilder extends Object /// Returns an [TypeName] AST representing the builder. TypeName buildType([Scope scope]) { return new TypeName( - getIdentifier( + identifier( scope, _name, - _importFrom != null ? Uri.parse(_importFrom) : null, + _importFrom, ), null, ); diff --git a/lib/src/scope.dart b/lib/src/scope.dart new file mode 100644 index 0000000..dd81a8e --- /dev/null +++ b/lib/src/scope.dart @@ -0,0 +1,116 @@ +// 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. + +import 'package:analyzer/analyzer.dart'; + +import 'builders/file.dart'; +import 'tokens.dart'; + +/// Returns an identifier for [name], using [scope] to enforce prefixing. +/// +/// It is _required_ within `code_builder` to use this API instead of creating +/// identifeirs manually in places where an obvious API collision could occur +/// (i.e. imported references). +/// +/// If [scope] is null, no prefixing is applied. +/// +/// ## Example +/// // May output `hello.Hello`. +/// identifier(scope, 'Hello', 'pacakge:hello/hello.dart') +Identifier identifier(Scope scope, String name, [String importFrom]) { + return (scope ?? Scope.identity).identifier(name, importFrom); +} + +/// Maintains an imported reference scope to avoid conflicts in generated code. +/// +/// ## Example +/// void useContext(Scope scope) { +/// // May print 'i1.Foo'. +/// print(scope.identifier('Foo', 'package:foo/foo.dart')); +/// +/// // May print 'i1.Bar'. +/// print(scope.identifier('Bar'), 'package:foo/foo.dart'); +/// +/// // May print 'i2.Bar'. +/// print(scope.getIdentifier('Baz'), 'package:bar/bar.dart'); +/// } +abstract class Scope { + /// A no-op [Scope]. Ideal for use for tests or example cases. + /// + /// **WARNING**: Does not collect import statements. This is only really + /// advisable for use in tests but not production code. To use import + /// collection but not prefixing, see [Scope.dedupe]. + static const Scope identity = const _IdentityScope(); + + /// Create a new scoping context. + /// + /// Actual implementation of is _not_ guaranteed, only that all import + /// prefixes will be unique in a given scope. + factory Scope() = _IncrementingScope; + + /// Create a context that just collects and de-duplicates imports. + /// + /// Prefixing is _not_ applied. + factory Scope.dedupe() => new _DeduplicatingScope(); + + /// Given a [name] and and import path, returns an [Identifier]. + Identifier identifier(String name, String importFrom); + + /// Return a list of import statements. + List toImports(); +} + +class _DeduplicatingScope extends _IdentityScope { + final Set _imports = new Set(); + + @override + Identifier identifier(String name, [String importFrom]) { + if (importFrom != null) { + _imports.add(importFrom); + } + return super.identifier(name); + } + + @override + List toImports() => + _imports.map((uri) => new ImportBuilder(uri)).toList(); +} + +class _IdentityScope implements Scope { + const _IdentityScope(); + + @override + Identifier identifier(String name, [_]) { + return new SimpleIdentifier(stringToken(name)); + } + + @override + List toImports() => const []; +} + +class _IncrementingScope extends _IdentityScope { + final Map _imports = {}; + + int _counter = 1; + + @override + Identifier identifier(String name, [String importFrom]) { + if (importFrom == null) { + return super.identifier(name); + } + var newId = _imports.putIfAbsent(importFrom, () => _counter++); + return new PrefixedIdentifier( + super.identifier('_i$newId'), + $period, + super.identifier(name), + ); + } + + @override + List toImports() { + return _imports.keys.map((uri) { + return new ImportBuilder(uri, prefix: '_i${_imports[uri]}'); + }).toList(); + } +} diff --git a/lib/testing.dart b/lib/testing.dart index dfba4b9..2437a97 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -11,9 +11,11 @@ import 'package:matcher/matcher.dart'; /// On failure, uses the default string matcher to show a detailed diff between /// the expected and actual source code results. /// -/// **NOTE*:: [source] needs to have `dartfmt` run on it, exact match only. +/// If [pretty] is set, uses another `toSource` method that adds additional +/// line breaks to make the output more readable and idiomatic. Matcher equalsSource( String source, { + bool pretty: false, Scope scope: Scope.identity, }) { var canParse = false; @@ -25,6 +27,7 @@ Matcher equalsSource( scope, source, canParse, + pretty, ); } @@ -32,8 +35,9 @@ class _EqualsSource extends Matcher { final Scope _scope; final String _source; final bool _canParse; + final bool _pretty; - _EqualsSource(this._scope, this._source, this._canParse); + _EqualsSource(this._scope, this._source, this._canParse, this._pretty); @override Description describe(Description description) { @@ -88,7 +92,7 @@ class _EqualsSource extends Matcher { String _formatAst(AstBuilder builder) { var astNode = builder.buildAst(_scope); if (_canParse) { - return dartfmt(astNode.toSource()); + return dartfmt(_pretty ? prettyToSource(astNode) : astNode.toSource()); } return astNode.toSource(); } diff --git a/test/builders/shared_test.dart b/test/builders/shared_test.dart index f7013dd..a38a062 100644 --- a/test/builders/shared_test.dart +++ b/test/builders/shared_test.dart @@ -14,8 +14,4 @@ void main() { test('stringToken should return a string token', () { expect(stringToken('Coffee').value().toString(), 'Coffee'); }); - - test('Scope.identity should return an unprefixed identifier', () { - expect(Scope.identity.getIdentifier('Coffee').toSource(), 'Coffee'); - }); } diff --git a/test/e2e_test.dart b/test/e2e_test.dart new file mode 100644 index 0000000..05a5475 --- /dev/null +++ b/test/e2e_test.dart @@ -0,0 +1,108 @@ +// 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. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +// Closely mirrors the API you'd need to generate dependency injection :) +void main() { + test('Should emit a complex generated file', () { + // Imports from an existing Dart library. + var appRef = reference('App', 'package:app/app.dart'); + var moduleRef = reference('Module', 'package:app/app.dart'); + var thingRef = reference('Thing', 'package:app/app.dart'); + + var clazz = new ClassBuilder('Injector', asImplements: [appRef]); + clazz + ..addField( + new FieldBuilder.asFinal('_module', type: moduleRef), + ) + ..addConstructor(new ConstructorBuilder() + ..addPositional( + new ParameterBuilder('_module'), + asField: true, + )) + ..addMethod(new MethodBuilder( + 'getThing', + returns: thingRef.newInstance([ + reference('_module').invoke('getDep1', []), + reference('_module').invoke('getDep2', []), + ]), + returnType: thingRef, + )..addAnnotation(core.override)); + var lib = new LibraryBuilder() + ..addDirective( + new ImportBuilder('app.dart'), + ) + ..addMember( + clazz, + ); + expect( + lib, + equalsSource( + r''' + import 'app.dart'; + + class Injector implements App { + final Module _module; + + Injector(this._module); + + @override + Thing getThing() => new Thing(_module.getDep1(), _module.getDep2()); + } + ''', + pretty: true, + ), + ); + }); + + test('Should emit a complex generated file with scoping applied', () { + var appRef = reference('App', 'package:app/app.dart'); + var moduleRef = reference('Module', 'package:app/app.dart'); + var thingRef = reference('Thing', 'package:app/thing.dart'); + var lib = new LibraryBuilder.scope() + ..addMember(new ClassBuilder('Injector', asImplements: [appRef]) + ..addField( + new FieldBuilder.asFinal( + '_module', + type: moduleRef, + ), + ) + ..addConstructor(new ConstructorBuilder() + ..addPositional( + new ParameterBuilder('_module'), + asField: true, + )) + ..addMethod(new MethodBuilder( + 'getThing', + returnType: thingRef, + returns: thingRef.newInstance([ + reference('_module').invoke('getDep1', []), + reference('_module').invoke('getDep2', []), + ]), + )..addAnnotation(core.override))); + expect( + lib, + equalsSource( + r''' + import 'package:app/app.dart' as _i1; + import 'package:app/thing.dart' as _i2; + + class Injector implements _i1.App { + final _i1.Module _module; + + Injector(this._module); + + @override + _i2.Thing getThing() => new _i2.Thing(_module.getDep1(), _module.getDep2()); + } + ''', + pretty: true, + ), + ); + }); +} diff --git a/test/scope_test.dart b/test/scope_test.dart new file mode 100644 index 0000000..64b41d5 --- /dev/null +++ b/test/scope_test.dart @@ -0,0 +1,84 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/scope.dart'; +import 'package:test/test.dart'; + +void main() { + Scope scope; + + tearDown(() => scope = null); + + test('Identity scope should do nothing', () { + scope = Scope.identity; + + var identifiers = [ + scope.identifier('Foo', 'package:foo/foo.dart'), + scope.identifier('Bar', 'package:foo/foo.dart'), + scope.identifier('Baz', 'package:baz/baz.dart'), + ].map((i) => i.toSource()); + + expect( + identifiers, + [ + 'Foo', + 'Bar', + 'Baz', + ], + ); + + expect(scope.toImports(), isEmpty); + }); + + test('Deduplicating scope should deduplicate imports', () { + scope = new Scope.dedupe(); + + var identifiers = [ + scope.identifier('Foo', 'package:foo/foo.dart'), + scope.identifier('Bar', 'package:foo/foo.dart'), + scope.identifier('Baz', 'package:baz/baz.dart'), + ].map((i) => i.toSource()); + + expect( + identifiers, + [ + 'Foo', + 'Bar', + 'Baz', + ], + ); + + expect( + scope.toImports().map((i) => i.buildAst().toSource()), + [ + r"import 'package:foo/foo.dart';", + r"import 'package:baz/baz.dart';", + ], + ); + }); + + test('Default scope should auto-prefix', () { + scope = new Scope(); + + var identifiers = [ + scope.identifier('Foo', 'package:foo/foo.dart'), + scope.identifier('Bar', 'package:foo/foo.dart'), + scope.identifier('Baz', 'package:baz/baz.dart'), + ].map((i) => i.toSource()); + + expect( + identifiers, + [ + '_i1.Foo', + '_i1.Bar', + '_i2.Baz', + ], + ); + + expect( + scope.toImports().map((i) => i.buildAst().toSource()), + [ + r"import 'package:foo/foo.dart' as _i1;", + r"import 'package:baz/baz.dart' as _i2;", + ], + ); + }); +} From 8b8613f51d2f53981d55e79925b9d1849e3ceebb Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 20 Oct 2016 11:35:10 -0700 Subject: [PATCH 16/19] Update docs, pubspec. --- CHANGELOG.md | 4 ++++ README.md | 44 +++++++++++++++++++------------------------- pubspec.yaml | 2 +- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb8ebe..ffc86f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.0-alpha + +- Large refactor that makes the library more feature complete. + ## 0.1.1 - Add concept of `Scope` and change `toAst` to support it diff --git a/README.md b/README.md index 7b28d6a..297b8b7 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,14 @@ Code builder has a narrow and user-friendly API. For example creating a class with a method: ```dart -new ClassBuilder('Animal', extends: 'Organism') - ..addMethod(new MethodBuilder.returnVoid('eat') - ..setExpression(new ExpressionBuilder.invoke('print', - positional: [new LiteralString('Yum!')]))); +var base = reference('Organism'); +var clazz = new ClassBuilder('Animal', asExtends: base); +clazz.addMethod( + new MethodBuilder.returnVoid( + 'eat', + returns: reference('print).call([literal('Yum')]), + ), +); ``` Outputs: @@ -54,26 +58,16 @@ use prefixes to avoid symbol conflicts: ```dart var lib = new LibraryBuilder.scope() - ..addDeclaration(new MethodBuilder( - name: 'doThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - )) - ..addDeclaration(new MethodBuilder( - name: 'doOtherThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/alternative.dart', - )) - ..addParameter(new ParameterBuilder( - 'thing', - type: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - ))); + ..addMembers([ + new MethodBuilder( + 'doThing', + returnType: reference('Thing', 'package:thing/thing.dart'), + ), + new MethodBuilder( + 'doOtherThing', + returnType: reference('Thing', 'package:thing/alternative.dart'), + ), + ]); ``` Outputs: @@ -82,5 +76,5 @@ import 'package:thing/thing.dart' as _i1; import 'package:thing/alternative.dart' as _i2; _i1.Thing doThing() {} -_i2.Thing doOtherThing(_i1.Thing thing) {} +_i2.Thing doOtherThing() {} ``` diff --git a/pubspec.yaml b/pubspec.yaml index 21acd58..84f1bd9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 1.0.0-dev +version: 1.0.0-alpha description: A fluent API for generating Dart code author: Dart Team homepage: https://github.com/dart-lang/code_builder From cacaa75d5d21416ffcc43a15ddd1c1c532dffd85 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 27 Oct 2016 00:55:52 -0700 Subject: [PATCH 17/19] Address comments. Address comments and presubmit script. . --- .travis.yml | 2 +- README.md | 2 +- lib/code_builder.dart | 15 ------------- lib/dart/async.dart | 12 +++++----- lib/dart/core.dart | 8 +++---- lib/src/builders/annotation.dart | 4 ++-- lib/src/builders/class.dart | 4 ++-- lib/src/builders/expression.dart | 16 +++++++++++--- lib/src/builders/expression/assign.dart | 7 +++--- lib/src/builders/expression/negate.dart | 17 +++++++++++++++ lib/src/builders/field.dart | 4 ++-- lib/src/builders/file.dart | 4 ++-- lib/src/builders/method.dart | 4 ++-- lib/src/builders/parameter.dart | 2 +- lib/src/builders/type.dart | 4 ++-- lib/src/builders/type/new_instance.dart | 4 ++-- lib/src/pretty_printer.dart | 12 +++++++++- lib/src/scope.dart | 8 +++---- lib/testing.dart | 1 + test/builders/class_test.dart | 24 ++++++++++---------- test/builders/expression_test.dart | 4 ++-- test/builders/field_test.dart | 6 ++--- test/builders/method_test.dart | 10 ++++----- test/builders/statement_test.dart | 16 +++++++------- test/builders/type_test.dart | 9 ++++---- test/e2e_test.dart | 4 ++-- tool/presubmit.sh | 29 +++++++++++++++++++++++++ 27 files changed, 143 insertions(+), 89 deletions(-) create mode 100755 tool/presubmit.sh diff --git a/.travis.yml b/.travis.yml index 5826f9b..08997a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ dart: - dev - stable -script: ./tool/travis.sh +script: ./tool/presubmit.sh diff --git a/README.md b/README.md index 297b8b7..df51b80 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ var clazz = new ClassBuilder('Animal', asExtends: base); clazz.addMethod( new MethodBuilder.returnVoid( 'eat', - returns: reference('print).call([literal('Yum')]), + returns: reference('print').call([literal('Yum')]), ), ); ``` diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 4506474..9506468 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -2,9 +2,6 @@ // 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. -import 'package:dart_style/dart_style.dart'; -import 'package:meta/meta.dart'; - export 'src/builders/annotation.dart' show AnnotationBuilder; export 'src/builders/class.dart' show asStatic, clazz, extend, implement, mixin, ClassBuilder; @@ -34,15 +31,3 @@ export 'src/builders/shared.dart' show AstBuilder, Scope; export 'src/builders/statement.dart' show ifThen, elseIf, elseThen, IfStatementBuilder, StatementBuilder; export 'src/builders/type.dart' show NewInstanceBuilder, TypeBuilder; - -final _dartFmt = new DartFormatter(); - -/// Returns [source] formatted by `dartfmt`. -@visibleForTesting -String dartfmt(String source) { - try { - return _dartFmt.format(source); - } on FormatterException catch (_) { - return _dartFmt.formatStatement(source); - } -} diff --git a/lib/dart/async.dart b/lib/dart/async.dart index fcb51b6..6025c39 100644 --- a/lib/dart/async.dart +++ b/lib/dart/async.dart @@ -2,7 +2,7 @@ // 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. -/// Contains reference to the `dart:async` SDK for use in code generation. +/// Contains references to the `dart:async` SDK for use in code generation. /// /// This library is currently *experimental*, and is subject to change; it is /// currently manually maintained but there might be a strong use case for this @@ -14,11 +14,11 @@ /// import 'package:code_builder/code_builder.dart'; /// import 'package:code_builder/dart/async.dart'; /// -/// All references are _namespaced_ under [async]. Try it: +/// All references are _namespaced_ under [lib$async]. Try it: /// // Outputs: new Future.value('Hello') /// async.Future.newInstanceNamed('value', [literal('Hello')]); /// -/// If you are [missing a reference from `dart:async`](https://goo.gl/XbSfmT) +/// If you are [missing a symbol from `dart:async`](https://goo.gl/XbSfmT) /// please send us a [pull request](https://goo.gl/2LvV7f) or /// [file an issue](https://goo.gl/IooPfl). library code_builder.dart.async; @@ -27,10 +27,10 @@ import 'dart:async' as dart_async; import 'package:code_builder/code_builder.dart'; -/// A namespace for references in `dart:async`. -final DartAsync async = new DartAsync._(); +/// References to `dart:async`. +final DartAsync lib$async = new DartAsync._(); -/// References to the `dart:async` library for code generation. See [async]. +/// References to the `dart:async` library for code generation. See [lib$async]. class DartAsync { /// References [dart_async.Future]. final ReferenceBuilder Future = _ref('Future'); diff --git a/lib/dart/core.dart b/lib/dart/core.dart index 21e97b3..9a5a0d7 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -2,7 +2,7 @@ // 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. -/// Contains reference to the `dart:core` SDK for use in code generation. +/// Contains references to the `dart:core` SDK for use in code generation. /// /// This library is currently *experimental*, and is subject to change; it is /// currently manually maintained but there might be a strong use case for this @@ -14,7 +14,7 @@ /// import 'package:code_builder/code_builder.dart'; /// import 'package:code_builder/dart/core.dart'; /// -/// All references are _namespaced_ under [core]. Try it: +/// All references are _namespaced_ under [lib$core]. Try it: /// // Outputs: print('Hello World') /// core.print.call([literal('Hello World')]); /// @@ -28,9 +28,9 @@ import 'dart:core' as dart_core; import 'package:code_builder/code_builder.dart'; /// A namespace for references in `dart:core`. -final DartCore core = new DartCore._(); +final DartCore lib$core = new DartCore._(); -/// References to the `dart:core` library for code generation. See [core]. +/// References to the `dart:core` library for code generation. See [lib$core]. class DartCore { /// References [dart_core.bool]. final ReferenceBuilder bool = _ref('bool'); diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart index eb68c15..404088a 100644 --- a/lib/src/builders/annotation.dart +++ b/lib/src/builders/annotation.dart @@ -7,7 +7,7 @@ import 'package:code_builder/src/builders/class.dart'; import 'package:code_builder/src/builders/parameter.dart'; import 'package:code_builder/src/builders/shared.dart'; -/// Lazily builds an [Annotation] AST when [buildAnnotation] is invoked. +/// Builds an [Annotation] AST when [buildAnnotation] is invoked. abstract class AnnotationBuilder implements ValidClassMember, ValidParameterMember { /// Returns an [Annotation] AST representing the builder. @@ -43,7 +43,7 @@ abstract class HasAnnotationsMixin implements HasAnnotations { .toList(); /// Clones all annotations to [clone]. - void cloneAnnotationsTo(HasAnnotations clone) { + void copyAnnotationsTo(HasAnnotations clone) { clone.addAnnotations(_annotations); } } diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart index 5a035a5..333d3e4 100644 --- a/lib/src/builders/class.dart +++ b/lib/src/builders/class.dart @@ -49,7 +49,7 @@ ClassBuilder clazz( } /// Wrap [member] to be emitted as a `static` method or field. -_StaticFieldWrapper asStatic(ValidClassMember member) { +ValidClassMember asStatic(ValidClassMember member) { return new _StaticFieldWrapper(member); } @@ -198,7 +198,7 @@ class _ClassBuilderImpl extends Object ClassDeclaration buildClass([Scope scope]) { var extend = _extends; if (extend == null && _with.isNotEmpty) { - extend = core.Object; + extend = lib$core.Object; } final clazz = new ClassDeclaration( null, diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 815cdb4..9b3b289 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -109,7 +109,11 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { StatementBuilder asAssert() => new _AsAssert(this); @override - StatementBuilder asAssign(String variable) => new _AsAssign(this, variable); + StatementBuilder asAssign( + String variable, { + bool nullAware: false, + }) => + new _AsAssign(this, variable, nullAware); @override StatementBuilder asConst(String variable, [TypeBuilder type]) { @@ -162,7 +166,7 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { @override ExpressionBuilder identical(ExpressionBuilder other) { - return core.identical.call([ + return lib$core.identical.call([ this, other, ]); @@ -183,6 +187,9 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { @override ExpressionBuilder negate() => new _NegateExpression(this); + @override + ExpressionBuilder negative() => new _NegativeExpression(this); + @override ExpressionBuilder notEquals(ExpressionBuilder other) { return new _AsBinaryExpression( @@ -196,7 +203,7 @@ abstract class AbstractExpressionMixin implements ExpressionBuilder { ExpressionBuilder parentheses() => new _ParenthesesExpression(this); } -/// Lazily builds an [Expression] AST when [buildExpression] is invoked. +/// Builds an [Expression] AST when [buildExpression] is invoked. abstract class ExpressionBuilder implements AstBuilder, StatementBuilder, ValidParameterMember { /// Returns as an [ExpressionBuilder] multiplying by [other]. @@ -266,6 +273,9 @@ abstract class ExpressionBuilder /// Returns as an [ExpressionBuilder] negating using the `!` operator. ExpressionBuilder negate(); + /// Returns as an [ExpressionBuilder] negating the value, + ExpressionBuilder negative(); + /// Returns as an [ExpressionBuilder] comparing using `!=` against [other]. ExpressionBuilder notEquals(ExpressionBuilder other); diff --git a/lib/src/builders/expression/assign.dart b/lib/src/builders/expression/assign.dart index 168eb73..5c6b191 100644 --- a/lib/src/builders/expression/assign.dart +++ b/lib/src/builders/expression/assign.dart @@ -1,10 +1,11 @@ part of code_builder.src.builders.expression; class _AsAssign extends AbstractExpressionMixin { - final ExpressionBuilder _value; final String _name; + final bool _nullAware; + final ExpressionBuilder _value; - _AsAssign(this._value, this._name); + _AsAssign(this._value, this._name, this._nullAware); @override AstNode buildAst([Scope scope]) => buildExpression(scope); @@ -13,7 +14,7 @@ class _AsAssign extends AbstractExpressionMixin { Expression buildExpression([Scope scope]) { return new AssignmentExpression( stringIdentifier(_name), - $equals, + _nullAware ? $nullAwareEquals : $equals, _value.buildExpression(scope), ); } diff --git a/lib/src/builders/expression/negate.dart b/lib/src/builders/expression/negate.dart index 1bac44b..d1d58d3 100644 --- a/lib/src/builders/expression/negate.dart +++ b/lib/src/builders/expression/negate.dart @@ -16,3 +16,20 @@ class _NegateExpression extends AbstractExpressionMixin { ); } } + +class _NegativeExpression extends AbstractExpressionMixin { + final ExpressionBuilder _expression; + + _NegativeExpression(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new PrefixExpression( + $minus, + _expression.parentheses().buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/field.dart b/lib/src/builders/field.dart index 0286646..51b5d97 100644 --- a/lib/src/builders/field.dart +++ b/lib/src/builders/field.dart @@ -22,7 +22,7 @@ FieldBuilder varField( ); } -/// Short-hand for [FieldBuilder]. +/// Short-hand for [FieldBuilder.asFinal]. FieldBuilder varFinal( String name, { TypeBuilder type, @@ -35,7 +35,7 @@ FieldBuilder varFinal( ); } -/// Short-hand for [FieldBuilder]. +/// Short-hand for [FieldBuilder.asConst]. FieldBuilder varConst( String name, { TypeBuilder type, diff --git a/lib/src/builders/file.dart b/lib/src/builders/file.dart index dd82969..fc6e71b 100644 --- a/lib/src/builders/file.dart +++ b/lib/src/builders/file.dart @@ -6,7 +6,7 @@ import 'package:analyzer/analyzer.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/tokens.dart'; -/// Lazily builds a file of Dart source code. +/// Builds a file of Dart source code. /// /// See [LibraryBuilder] and [PartBuilder] for concrete implementations. abstract class FileBuilder implements AstBuilder { @@ -26,7 +26,7 @@ abstract class FileBuilder implements AstBuilder { } } -/// Lazily builds a standalone file (library) of Dart source code. +/// Builds a standalone file (library) of Dart source code. class LibraryBuilder extends FileBuilder { final List> _directives = >[]; final Scope _scope; diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart index 4461486..2228d2f 100644 --- a/lib/src/builders/method.dart +++ b/lib/src/builders/method.dart @@ -209,12 +209,12 @@ abstract class MethodBuilder /// Creates a new [MethodBuilder] that returns `void`. factory MethodBuilder.returnVoid(String name, {ExpressionBuilder returns}) { if (returns == null) { - return new _MethodBuilderImpl(name, returns: core.$void); + return new _MethodBuilderImpl(name, returns: lib$core.$void); } return new _LambdaMethodBuilder( name, returns, - core.$void, + lib$core.$void, null, ); } diff --git a/lib/src/builders/parameter.dart b/lib/src/builders/parameter.dart index c37ae27..58df28e 100644 --- a/lib/src/builders/parameter.dart +++ b/lib/src/builders/parameter.dart @@ -10,7 +10,7 @@ import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/type.dart'; import 'package:code_builder/src/tokens.dart'; -/// A more short-hand way of constructing a [ParameterBuilder]. +/// A short-hand way of constructing a [ParameterBuilder]. ParameterBuilder parameter( String name, [ Iterable members = const [], diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart index 34d21ba..c4eae10 100644 --- a/lib/src/builders/type.dart +++ b/lib/src/builders/type.dart @@ -29,7 +29,7 @@ abstract class AbstractTypeBuilderMixin { } /// Invokes `const` on this type with a [name]d constructor. - NewInstanceBuilder constInstanceWith( + NewInstanceBuilder namedConstInstance( String name, Iterable positional, [ Map named = const {}, @@ -50,7 +50,7 @@ abstract class AbstractTypeBuilderMixin { } /// Invokes `new` on this type with a [name]d constructor. - NewInstanceBuilder newInstanceWith( + NewInstanceBuilder namedNewInstance( String name, Iterable positional, [ Map named = const {}, diff --git a/lib/src/builders/type/new_instance.dart b/lib/src/builders/type/new_instance.dart index 66ceac3..c2d9b68 100644 --- a/lib/src/builders/type/new_instance.dart +++ b/lib/src/builders/type/new_instance.dart @@ -4,9 +4,9 @@ part of code_builder.src.builders.type; /// /// See [TypeBuilder]: /// - [TypeBuilder.constInstance] -/// - [TypeBuilder.constInstanceWith] +/// - [TypeBuilder.namedConstInstance] /// - [TypeBuilder.newInstance] -/// - [TypeBuilder.newInstanceWith] +/// - [TypeBuilder.namedNewInstance] abstract class NewInstanceBuilder implements AnnotationBuilder, InvocationBuilder { factory NewInstanceBuilder._const(TypeBuilder type, [String name]) { diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart index 9bb1d08..53d3324 100644 --- a/lib/src/pretty_printer.dart +++ b/lib/src/pretty_printer.dart @@ -4,11 +4,21 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/token.dart'; -import 'package:code_builder/code_builder.dart'; import 'package:dart_style/dart_style.dart'; import 'analyzer_patch.dart'; +final _dartFmt = new DartFormatter(); + +/// Returns [source] formatted by `dartfmt`. +String dartfmt(String source) { + try { + return _dartFmt.format(source); + } on FormatterException catch (_) { + return _dartFmt.formatStatement(source); + } +} + /// Augments [AstNode.toSource] by adding some whitespace/line breaks. /// /// The final result is run through `dartfmt`. diff --git a/lib/src/scope.dart b/lib/src/scope.dart index dd81a8e..f6f6ec4 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -30,10 +30,10 @@ Identifier identifier(Scope scope, String name, [String importFrom]) { /// print(scope.identifier('Foo', 'package:foo/foo.dart')); /// /// // May print 'i1.Bar'. -/// print(scope.identifier('Bar'), 'package:foo/foo.dart'); +/// print(scope.identifier('Bar', 'package:foo/foo.dart')); /// /// // May print 'i2.Bar'. -/// print(scope.getIdentifier('Baz'), 'package:bar/bar.dart'); +/// print(scope.getIdentifier('Baz', 'package:bar/bar.dart')); /// } abstract class Scope { /// A no-op [Scope]. Ideal for use for tests or example cases. @@ -45,8 +45,8 @@ abstract class Scope { /// Create a new scoping context. /// - /// Actual implementation of is _not_ guaranteed, only that all import - /// prefixes will be unique in a given scope. + /// Actual implementation of [Scope] is _not_ guaranteed, only that all + /// import prefixes will be unique in a given scope. factory Scope() = _IncrementingScope; /// Create a context that just collects and de-duplicates imports. diff --git a/lib/testing.dart b/lib/testing.dart index 2437a97..ef6ae98 100644 --- a/lib/testing.dart +++ b/lib/testing.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/pretty_printer.dart'; import 'package:dart_style/dart_style.dart'; import 'package:matcher/matcher.dart'; diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart index 07e205a..40fe307 100644 --- a/test/builders/class_test.dart +++ b/test/builders/class_test.dart @@ -85,7 +85,7 @@ void main() { expect( clazz('Animal', [ constructor([ - parameter('name', [core.String]), + parameter('name', [lib$core.String]), thisField( named( parameter('age').asOptional(literal(0)), @@ -105,17 +105,17 @@ void main() { expect( clazz('Animal', [ asStatic( - varField('static1', type: core.String, value: literal('Hello')), + varField('static1', type: lib$core.String, value: literal('Hello')), ), asStatic( - varFinal('static2', type: core.List, value: literal([])), + varFinal('static2', type: lib$core.List, value: literal([])), ), asStatic( - varConst('static3', type: core.bool, value: literal(true)), + varConst('static3', type: lib$core.bool, value: literal(true)), ), - varField('var1', type: core.String, value: literal('Hello')), - varFinal('var2', type: core.List, value: literal([])), - varConst('var3', type: core.bool, value: literal(true)), + varField('var1', type: lib$core.String, value: literal('Hello')), + varFinal('var2', type: lib$core.List, value: literal([])), + varConst('var3', type: lib$core.bool, value: literal(true)), ]), equalsSource(r''' class Animal { @@ -134,15 +134,15 @@ void main() { expect( clazz('Animal', [ asStatic(method('staticMethod', [ - core.$void, - core.print.call([literal('Called staticMethod')]), + lib$core.$void, + lib$core.print.call([literal('Called staticMethod')]), ])), method('instanceMethod', [ - core.$void, - core.print.call([literal('Called instanceMethod')]), + lib$core.$void, + lib$core.print.call([literal('Called instanceMethod')]), ]), constructor([ - core.print.call([literal('Called constructor')]), + lib$core.print.call([literal('Called constructor')]), ]), ]), equalsSource(r''' diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart index ad054c8..c00265b 100644 --- a/test/builders/expression_test.dart +++ b/test/builders/expression_test.dart @@ -89,7 +89,7 @@ void main() { test('should emit a typed assignemnt statement', () { expect( - literal(true).asVar('flag', core.bool), + literal(true).asVar('flag', lib$core.bool), equalsSource(r''' bool flag = true; '''), @@ -116,7 +116,7 @@ void main() { test('should call an expression as a function', () { expect( - core.identical.call([literal(true), literal(true)]), + lib$core.identical.call([literal(true), literal(true)]), equalsSource(r''' identical(true, true) '''), diff --git a/test/builders/field_test.dart b/test/builders/field_test.dart index 38511d0..fcadb66 100644 --- a/test/builders/field_test.dart +++ b/test/builders/field_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; void main() { test('emit a var', () { expect( - varField('a', type: core.String, value: literal('Hello')), + varField('a', type: lib$core.String, value: literal('Hello')), equalsSource(r''' String a = 'Hello'; '''), @@ -15,7 +15,7 @@ void main() { test('emit a final', () { expect( - varFinal('a', type: core.String, value: literal('Hello')), + varFinal('a', type: lib$core.String, value: literal('Hello')), equalsSource(r''' final String a = 'Hello'; '''), @@ -24,7 +24,7 @@ void main() { test('emit a const', () { expect( - varConst('a', type: core.String, value: literal('Hello')), + varConst('a', type: lib$core.String, value: literal('Hello')), equalsSource(r''' const String a = 'Hello'; '''), diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart index 6b12ec8..e532e62 100644 --- a/test/builders/method_test.dart +++ b/test/builders/method_test.dart @@ -20,7 +20,7 @@ void main() { test('should emit a top-level void main() function', () { expect( method('main', [ - core.$void, + lib$core.$void, ]).buildMethod(false).toSource(), equalsIgnoringWhitespace(r''' void main(); @@ -31,7 +31,7 @@ void main() { test('should emit a function with a parameter', () { expect( method('main', [ - parameter('args', [core.List]), + parameter('args', [lib$core.List]), ]).buildMethod(false).toSource(), equalsIgnoringWhitespace(r''' main(List args); @@ -123,7 +123,7 @@ void main() { test('should a method with a lambda value', () { expect( - lambda('supported', literal(true), returnType: core.bool), + lambda('supported', literal(true), returnType: lib$core.bool), equalsSource(r''' bool supported() => true; '''), @@ -144,7 +144,7 @@ void main() { expect( getter( 'values', - returnType: core.Iterable, + returnType: lib$core.Iterable, statements: [ literal([]).asReturn(), ], @@ -159,7 +159,7 @@ void main() { test('should emit a setter', () { expect( - setter('name', parameter('name', [core.String]), [ + setter('name', parameter('name', [lib$core.String]), [ (reference('name') + literal('!')).asAssign('_name'), ]), equalsSource(r''' diff --git a/test/builders/statement_test.dart b/test/builders/statement_test.dart index f6a0eb4..b899c85 100644 --- a/test/builders/statement_test.dart +++ b/test/builders/statement_test.dart @@ -8,7 +8,7 @@ void main() { test('should emit a simple if statement', () { expect( ifThen(literal(true), [ - core.print.call([literal('Hello World')]), + lib$core.print.call([literal('Hello World')]), ]), equalsSource(r''' if (true) { @@ -21,9 +21,9 @@ void main() { test('should emit an if then else statement', () { expect( ifThen(literal(true), [ - core.print.call([literal('TRUE')]), + lib$core.print.call([literal('TRUE')]), elseThen([ - core.print.call([literal('FALSE')]), + lib$core.print.call([literal('FALSE')]), ]), ]), equalsSource(r''' @@ -40,9 +40,9 @@ void main() { final a = reference('a'); expect( ifThen(a.equals(literal(1)), [ - core.print.call([literal('Was 1')]), + lib$core.print.call([literal('Was 1')]), elseIf(ifThen(a.equals(literal(2)), [ - core.print.call([literal('Was 2')]), + lib$core.print.call([literal('Was 2')]), ])), ]), equalsSource(r''' @@ -61,11 +61,11 @@ void main() { ifThen( a.equals(literal(1)), [ - core.print.call([literal('Was 1')]), + lib$core.print.call([literal('Was 1')]), elseIf(ifThen(a.equals(literal(2)), [ - core.print.call([literal('Was 2')]), + lib$core.print.call([literal('Was 2')]), elseThen([ - core.print.call([literal('Was ') + a]), + lib$core.print.call([literal('Was ') + a]), ]), ])), ], diff --git a/test/builders/type_test.dart b/test/builders/type_test.dart index f2eaa56..b9ac7b8 100644 --- a/test/builders/type_test.dart +++ b/test/builders/type_test.dart @@ -7,7 +7,7 @@ void main() { group('new instance', () { test('emits a new List', () { expect( - core.List.newInstance([]), + lib$core.List.newInstance([]), equalsSource(r''' new List() '''), @@ -16,7 +16,7 @@ void main() { test('emits a new List.from', () { expect( - core.List.newInstanceWith('from', [ + lib$core.List.namedNewInstance('from', [ literal([1, 2, 3]), ]), equalsSource(r''' @@ -40,7 +40,8 @@ void main() { test('emits a const constructor as an annotation', () { expect( clazz('Animal', [ - core.Deprecated.constInstance([literal('Animals are out of style')]), + lib$core.Deprecated + .constInstance([literal('Animals are out of style')]), ]), equalsSource(r''' @Deprecated('Animals are out of style') @@ -52,7 +53,7 @@ void main() { test('emits a named const constructor as an annotation', () { expect( clazz('Animal', [ - reference('Component').constInstanceWith( + reference('Component').namedConstInstance( 'stateful', [], { diff --git a/test/e2e_test.dart b/test/e2e_test.dart index 05a5475..1c88780 100644 --- a/test/e2e_test.dart +++ b/test/e2e_test.dart @@ -32,7 +32,7 @@ void main() { reference('_module').invoke('getDep2', []), ]), returnType: thingRef, - )..addAnnotation(core.override)); + )..addAnnotation(lib$core.override)); var lib = new LibraryBuilder() ..addDirective( new ImportBuilder('app.dart'), @@ -84,7 +84,7 @@ void main() { reference('_module').invoke('getDep1', []), reference('_module').invoke('getDep2', []), ]), - )..addAnnotation(core.override))); + )..addAnnotation(lib$core.override))); expect( lib, equalsSource( diff --git a/tool/presubmit.sh b/tool/presubmit.sh new file mode 100755 index 0000000..035e29e --- /dev/null +++ b/tool/presubmit.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Make sure dartfmt is run on everything +# This assumes you have dart_style as a dev_dependency +echo "Checking dartfmt..." +NEEDS_DARTFMT="$(find lib test -name "*.dart" | xargs pub run dart_style:format -n)" +if [[ ${NEEDS_DARTFMT} != "" ]] +then + echo "FAILED" + echo "${NEEDS_DARTFMT}" + exit 1 +fi +echo "PASSED" + +# Make sure we pass the analyzer +echo "Checking dartanalyzer..." +FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)" +if [[ $FAILS_ANALYZER == *"[error]"* ]] +then + echo "FAILED" + echo "${FAILS_ANALYZER}" + exit 1 +fi +echo "PASSED" + +# Fail on anything that fails going forward. +set -e + +pub run test From 5f4743f5f275ac374ed2e750e613eb6983acc58b Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 27 Oct 2016 01:16:57 -0700 Subject: [PATCH 18/19] . --- lib/src/builders/expression.dart | 4 ++-- test/builders/expression_test.dart | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart index 9b3b289..faa78c7 100644 --- a/lib/src/builders/expression.dart +++ b/lib/src/builders/expression.dart @@ -222,7 +222,7 @@ abstract class ExpressionBuilder StatementBuilder asAssert(); /// Returns as a [StatementBuilder] that assigns to an existing [variable]. - StatementBuilder asAssign(String variable); + StatementBuilder asAssign(String variable, {bool nullAware}); /// Returns as a [StatementBuilder] that assigns to a new `const` [variable]. StatementBuilder asConst(String variable, [TypeBuilder type]); @@ -231,7 +231,7 @@ abstract class ExpressionBuilder StatementBuilder asFinal(String variable, [TypeBuilder type]); /// Returns as a [StatementBuilder] that builds an `if` statement. - /*If*/ StatementBuilder asIf(); + IfStatementBuilder asIf(); /// Returns as a [StatementBuilder] that `return`s this expression. StatementBuilder asReturn(); diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart index c00265b..f9b6df1 100644 --- a/test/builders/expression_test.dart +++ b/test/builders/expression_test.dart @@ -60,6 +60,15 @@ void main() { ); }); + test('should emit an assign expression with a null aware', () { + expect( + literal(true).asAssign('flag', nullAware: true), + equalsSource(r''' + flag ??= true + '''), + ); + }); + test('should emit a const variable assignment statement', () { expect( literal(true).asConst('flag'), @@ -226,4 +235,13 @@ void main() { '''), ); }); + + test('should return as a negative expression', () { + expect( + literal(1).negative(), + equalsSource(r''' + -(1) + '''), + ); + }); } From c443ccb5f40a6c063cf2ac8c3c8ef60b5dc808fa Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 27 Oct 2016 01:20:27 -0700 Subject: [PATCH 19/19] . --- test/builders/expression_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart index f9b6df1..5d6b453 100644 --- a/test/builders/expression_test.dart +++ b/test/builders/expression_test.dart @@ -62,7 +62,7 @@ void main() { test('should emit an assign expression with a null aware', () { expect( - literal(true).asAssign('flag', nullAware: true), + literal(true).asAssign('flag', nullAware: true), equalsSource(r''' flag ??= true '''),