diff --git a/lib/code_builder.dart b/lib/code_builder.dart index c52a078..077d5c2 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -8,12 +8,14 @@ export 'src/emitter.dart' show DartEmitter; export 'src/matchers.dart' show equalsDart; export 'src/specs/annotation.dart' show Annotation, AnnotationBuilder; export 'src/specs/class.dart' show Class, ClassBuilder; -export 'src/specs/code.dart' show Code, StaticCode, ScopedCode; +export 'src/specs/code.dart' + show Block, BlockBuilder, Code, StaticCode, ScopedCode; export 'src/specs/constructor.dart' show Constructor, ConstructorBuilder; export 'src/specs/directive.dart' show Directive, DirectiveType, DirectiveBuilder; export 'src/specs/expression.dart' show + AsCodeExpression, BinaryExpression, CodeExpression, Expression, diff --git a/lib/src/emitter.dart b/lib/src/emitter.dart index d408571..f3ec44c 100644 --- a/lib/src/emitter.dart +++ b/lib/src/emitter.dart @@ -25,7 +25,7 @@ import 'visitors.dart'; /// /// If [elements] is at least 2 elements, inserts [separator] delimiting them. @visibleForTesting -void visitAll( +StringSink visitAll( Iterable elements, StringSink output, void visit(T element), [ @@ -36,7 +36,7 @@ void visitAll( // // ... which would allocate more StringBuffer(s) for a one-time use. if (elements.isEmpty) { - return; + return output; } final iterator = elements.iterator..moveNext(); visit(iterator.current); @@ -44,6 +44,7 @@ void visitAll( output.write(separator); visit(iterator.current); } + return output; } class DartEmitter extends Object diff --git a/lib/src/matchers.dart b/lib/src/matchers.dart index 3183f4f..8c96df6 100644 --- a/lib/src/matchers.dart +++ b/lib/src/matchers.dart @@ -19,7 +19,7 @@ String _dartfmt(String source) { } catch (_) { // Ignored on purpose, probably not exactly valid Dart code. } finally { - source = source.replaceAll(' ', ' ').replaceAll('\n', '').trim(); + source = collapseWhitespace(source); } return source; } diff --git a/lib/src/specs/code.dart b/lib/src/specs/code.dart index afbd5da..a8414c4 100644 --- a/lib/src/specs/code.dart +++ b/lib/src/specs/code.dart @@ -4,14 +4,20 @@ library code_builder.src.specs.code; +import 'package:built_value/built_value.dart'; +import 'package:built_collection/built_collection.dart'; import 'package:meta/meta.dart'; import '../allocator.dart'; import '../base.dart'; +import '../emitter.dart'; import '../visitors.dart'; +import 'expression.dart'; import 'reference.dart'; +part 'code.g.dart'; + /// Returns a scoped symbol to [Reference], with an import prefix if needed. /// /// This is short-hand for [Allocator.allocate] in most implementations. @@ -41,10 +47,40 @@ abstract class Code implements Spec { R accept(covariant CodeVisitor visitor, [R context]); } +/// Represents blocks of statements of Dart code. +abstract class Block implements Built, Spec { + factory Block([void updates(BlockBuilder b)]) = _$Block; + + Block._(); + + @override + R accept(covariant CodeVisitor visitor, [R context]) { + return visitor.visitBlock(this, context); + } + + BuiltList get statements; +} + +abstract class BlockBuilder implements Builder { + factory BlockBuilder() = _$BlockBuilder; + + BlockBuilder._(); + + /// Adds an [expression] to [statements]. + /// + /// **NOTE**: Not all expressions are _useful_ statements. + void addExpression(Expression expression) { + statements.add(expression.asStatement()); + } + + ListBuilder statements = new ListBuilder(); +} + /// Knowledge of different types of blocks of code in Dart. /// /// **INTERNAL ONLY**. abstract class CodeVisitor implements SpecVisitor { + T visitBlock(Block code, [T context]); T visitStaticCode(StaticCode code, [T context]); T visitScopedCode(ScopedCode code, [T context]); } @@ -54,6 +90,14 @@ abstract class CodeEmitter implements CodeVisitor { @protected Allocator get allocator; + @override + visitBlock(Block block, [StringSink output]) { + output ??= new StringBuffer(); + return visitAll(block.statements, output, (statement) { + statement.accept(this, output); + }, '\n'); + } + @override visitStaticCode(StaticCode code, [StringSink output]) { output ??= new StringBuffer(); @@ -77,6 +121,9 @@ class StaticCode implements Code { R accept(CodeVisitor visitor, [R context]) { return visitor.visitStaticCode(this, context); } + + @override + String toString() => code; } /// Represents a [code] block that may require scoping. @@ -89,4 +136,7 @@ class ScopedCode implements Code { R accept(CodeVisitor visitor, [R context]) { return visitor.visitScopedCode(this, context); } + + @override + String toString() => code((ref) => ref.symbol); } diff --git a/lib/src/specs/code.g.dart b/lib/src/specs/code.g.dart new file mode 100644 index 0000000..c8d74bc --- /dev/null +++ b/lib/src/specs/code.g.dart @@ -0,0 +1,92 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of code_builder.src.specs.code; + +// ************************************************************************** +// Generator: BuiltValueGenerator +// ************************************************************************** + +// ignore_for_file: annotate_overrides +// ignore_for_file: prefer_expression_function_bodies +// ignore_for_file: sort_constructors_first + +class _$Block extends Block { + @override + final BuiltList statements; + + factory _$Block([void updates(BlockBuilder b)]) => + (new BlockBuilder()..update(updates)).build() as _$Block; + + _$Block._({this.statements}) : super._() { + if (statements == null) throw new ArgumentError.notNull('statements'); + } + + @override + Block rebuild(void updates(BlockBuilder b)) => + (toBuilder()..update(updates)).build(); + + @override + _$BlockBuilder toBuilder() => new _$BlockBuilder()..replace(this); + + @override + bool operator ==(dynamic other) { + if (identical(other, this)) return true; + if (other is! Block) return false; + return statements == other.statements; + } + + @override + int get hashCode { + return $jf($jc(0, statements.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('Block')..add('statements', statements)) + .toString(); + } +} + +class _$BlockBuilder extends BlockBuilder { + _$Block _$v; + + @override + ListBuilder get statements { + _$this; + return super.statements ??= new ListBuilder(); + } + + @override + set statements(ListBuilder statements) { + _$this; + super.statements = statements; + } + + _$BlockBuilder() : super._(); + + BlockBuilder get _$this { + if (_$v != null) { + super.statements = _$v.statements?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Block other) { + if (other == null) throw new ArgumentError.notNull('other'); + _$v = other as _$Block; + } + + @override + void update(void updates(BlockBuilder b)) { + if (updates != null) updates(this); + } + + @override + _$Block build() { + final _$result = _$v ?? new _$Block._(statements: statements?.build()); + replace(_$result); + return _$result; + } +} diff --git a/lib/src/specs/expression.dart b/lib/src/specs/expression.dart index 5daf622..a9c11e6 100644 --- a/lib/src/specs/expression.dart +++ b/lib/src/specs/expression.dart @@ -27,7 +27,12 @@ abstract class Expression implements Spec { R accept(covariant ExpressionVisitor visitor, [R context]); /// Returns the expression as a valid [Code] block. - Code asCode() => new _AsExpressionCode(this); + /// + /// Also see [asStatement]. + Code asCode() => new AsCodeExpression(this, false); + + /// Returns the expression asa valid [Code] block with a trailing `;`. + Code asStatement() => new AsCodeExpression(this, true); /// Returns the result of [this] `&&` [other]. Expression and(Expression other) { @@ -86,21 +91,30 @@ abstract class Expression implements Spec { } /// Represents a [code] block that wraps an [Expression]. -class _AsExpressionCode implements Code { +class AsCodeExpression implements Code { final Expression code; - const _AsExpressionCode(this.code); + /// Whether this code should be considered a _statement_. + final bool isStatement; + + @visibleForTesting + const AsCodeExpression(this.code, [this.isStatement = false]); @override R accept(CodeVisitor visitor, [R context]) { - return code.accept(visitor as ExpressionVisitor, context); + return (visitor as ExpressionVisitor) + .visitAsCodeExpression(this, context); } + + @override + String toString() => code.toString(); } /// Knowledge of different types of expressions in Dart. /// /// **INTERNAL ONLY**. abstract class ExpressionVisitor implements SpecVisitor { + T visitAsCodeExpression(AsCodeExpression code, [T context]); T visitBinaryExpression(BinaryExpression expression, [T context]); T visitCodeExpression(CodeExpression expression, [T context]); T visitInvokeExpression(InvokeExpression expression, [T context]); @@ -113,6 +127,16 @@ abstract class ExpressionVisitor implements SpecVisitor { /// /// **INTERNAL ONLY**. abstract class ExpressionEmitter implements ExpressionVisitor { + @override + visitAsCodeExpression(AsCodeExpression expression, [StringSink output]) { + output ??= new StringBuffer(); + expression.code.accept(this, output); + if (expression.isStatement) { + output.write(';'); + } + return output; + } + @override visitBinaryExpression(BinaryExpression expression, [StringSink output]) { output ??= new StringBuffer(); diff --git a/lib/src/specs/expression/invoke.dart b/lib/src/specs/expression/invoke.dart index b9e0edb..40dd785 100644 --- a/lib/src/specs/expression/invoke.dart +++ b/lib/src/specs/expression/invoke.dart @@ -41,6 +41,10 @@ class InvokeExpression extends Expression { R accept(ExpressionVisitor visitor, [R context]) { return visitor.visitInvokeExpression(this, context); } + + @override + String toString() => + '${type ?? ''} $target($positionalArguments, $namedArguments)'; } enum InvokeExpressionType { diff --git a/lib/src/specs/expression/literal.dart b/lib/src/specs/expression/literal.dart index fc02d80..92c17a1 100644 --- a/lib/src/specs/expression/literal.dart +++ b/lib/src/specs/expression/literal.dart @@ -95,6 +95,9 @@ class LiteralExpression extends Expression { R accept(ExpressionVisitor visitor, [R context]) { return visitor.visitLiteralExpression(this, context); } + + @override + String toString() => literal; } class LiteralListExpression extends Expression { @@ -108,6 +111,9 @@ class LiteralListExpression extends Expression { R accept(ExpressionVisitor visitor, [R context]) { return visitor.visitLiteralListExpression(this, context); } + + @override + String toString() => '[${values.map(literal).join(', ')}]'; } class LiteralMapExpression extends Expression { @@ -127,4 +133,7 @@ class LiteralMapExpression extends Expression { R accept(ExpressionVisitor visitor, [R context]) { return visitor.visitLiteralMapExpression(this, context); } + + @override + String toString() => '{$values}'; } diff --git a/test/specs/code/statement_test.dart b/test/specs/code/statement_test.dart new file mode 100644 index 0000000..834d2c7 --- /dev/null +++ b/test/specs/code/statement_test.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a block of code', () { + expect( + new Block((b) => b.statements.addAll([ + const Code('if (foo) {'), + const Code(' print(true);'), + const Code('}'), + ])), + equalsDart(r''' + if (foo) { + print(true); + } + '''), + ); + }); + + test('should emit a block of code including expressions', () { + expect( + new Block((b) => b.statements.addAll([ + const Code('if (foo) {'), + refer('print')([literalTrue]).asStatement(), + const Code('}'), + ])), + equalsDart(r''' + if (foo) { + print(true); + } + '''), + ); + }); +}