From 5760c97bd8c45fdd5e06dc7ffe479e15a911a658 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Sep 2016 21:18:04 -0700 Subject: [PATCH 1/2] Add full scoping support for Library --- CHANGELOG.md | 8 ++ lib/code_builder.dart | 63 ++++----- lib/src/builders/annotation_builder.dart | 24 +--- lib/src/builders/class_builder.dart | 111 ++++++++------- lib/src/builders/constructor_builder.dart | 64 ++++----- lib/src/builders/expression_builder.dart | 89 +++++++++--- lib/src/builders/field_builder.dart | 52 ++++--- lib/src/builders/file_builder.dart | 163 +++++++++++----------- lib/src/builders/method_builder.dart | 70 +++++----- lib/src/builders/parameter_builder.dart | 40 +++--- lib/src/builders/type_builder.dart | 10 +- lib/testing/equals_source.dart | 87 ++++++------ pubspec.yaml | 2 +- test/builders/class_builder_test.dart | 30 ++-- test/builders/file_builder_test.dart | 4 +- test/builders/method_builder_test.dart | 18 +-- test/integration_test.dart | 85 +++++++++-- test/scope_test.dart | 67 +++++---- 18 files changed, 560 insertions(+), 427 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a116899..1cb8ebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.1 + +- Add concept of `Scope` and change `toAst` to support it + +Now your entire AST tree can be scoped and import directives +automatically added to a `LibraryBuilder` for you if you use +`LibraryBuilder.scope`. + ## 0.1.0 - Initial version diff --git a/lib/code_builder.dart b/lib/code_builder.dart index fc800e7..b4f3c75 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -44,17 +44,38 @@ part 'src/builders/method_builder.dart'; part 'src/builders/parameter_builder.dart'; part 'src/builders/statement_builder.dart'; part 'src/builders/type_builder.dart'; - 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) => _dartfmt.format(source); + +// Creates a defensive copy of an AST node. +AstNode/*=E*/ _cloneAst/**/(AstNode/*=E*/ astNode) { + return new AstCloner().cloneNode/**/(astNode); +} + +Identifier _stringId(String s) { + return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0)); +} + +Literal _stringLit(String s) { + return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s); +} + /// Base class for building and emitting a Dart language [AstNode]. abstract class CodeBuilder { /// Returns a copy-safe [AstNode] representing the current builder state. - A toAst(); + /// + /// Uses [scope] to output an AST re-written to use appropriate prefixes. + A toAst([Scope scope = const Scope.identity()]); } -// Simplifies some of the builders by having a mutable node we clone from. +@Deprecated('Builders are all becoming lazy') abstract class _AbstractCodeBuilder extends CodeBuilder { final A _astNode; @@ -62,42 +83,8 @@ abstract class _AbstractCodeBuilder extends CodeBuilder { /// Returns a copy-safe [AstNode] representing the current builder state. @override - A toAst() => _cloneAst/**/(_astNode); + A toAst([_]) => _cloneAst/**/(_astNode); @override String toString() => '$runtimeType: ${_astNode.toSource()}'; } - -/// Marker interface for builders that need an import to work. -/// -/// **NOTE**: This currently (as of 0.2.0) has no effect. It is planned that -/// the [FileBuilder] will be able to act as a scope 'resolver' and subtly -/// rewrite the AST tree to use prefixing if required (or requested). -abstract class ScopeAware implements CodeBuilder { - @override - A toAst() => toScopedAst(const Scope.identity()); - - /// Creates a copy-safe [AstNode] representing the current builder state. - /// - /// Uses [scope] to output an AST re-written to use appropriate prefixes. - A toScopedAst(Scope scope) => throw new UnimplementedError(); -} - -// Creates a defensive copy of an AST node. -AstNode/*=E*/ _cloneAst/**/(AstNode/*=E*/ astNode) { - return new AstCloner().cloneNode/**/(astNode); -} - -final DartFormatter _dartfmt = new DartFormatter(); - -/// Returns [source] formatted by `dartfmt`. -@visibleForTesting -String dartfmt(String source) => _dartfmt.format(source); - -Literal _stringLit(String s) { - return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s); -} - -Identifier _stringId(String s) { - return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0)); -} diff --git a/lib/src/builders/annotation_builder.dart b/lib/src/builders/annotation_builder.dart index a209a46..1ce7b70 100644 --- a/lib/src/builders/annotation_builder.dart +++ b/lib/src/builders/annotation_builder.dart @@ -16,7 +16,7 @@ part of code_builder; /// void destroyTheWorld() { ... } /// /// To create a `@DoNotUse('Blows up')` use [AnnotationBuilder.invoke]. -abstract class AnnotationBuilder implements ScopeAware { +abstract class AnnotationBuilder implements CodeBuilder { /// Create a new annotated `const` [constructor] invocation. /// /// May optionally specify an [importFrom] to auto-prefix the annotation _if_ @@ -46,8 +46,7 @@ abstract class AnnotationBuilder implements ScopeAware { [String importFrom]) = _ReferenceAnnotationBuilder; } -class _ConstructorAnnotationBuilder extends ScopeAware - implements AnnotationBuilder { +class _ConstructorAnnotationBuilder implements AnnotationBuilder { final String _constructor; final ExpressionBuilder _expression; final String _importFrom; @@ -56,15 +55,12 @@ class _ConstructorAnnotationBuilder extends ScopeAware [this._importFrom]); @override - List get requiredImports => [_importFrom]; - - @override - Annotation toAst() { - var expressionAst = _expression.toAst(); + Annotation toAst([Scope scope = const Scope.identity()]) { + var expressionAst = _expression.toAst(scope); if (expressionAst is MethodInvocation) { return new Annotation( null, - _stringId(_constructor), + scope.getIdentifier(_constructor, _importFrom), null, null, // TODO(matanl): InvocationExpression needs to be public API. @@ -82,17 +78,11 @@ class _ReferenceAnnotationBuilder implements AnnotationBuilder { const _ReferenceAnnotationBuilder(this._reference, [this._importFrom]); @override - List get requiredImports => [_importFrom]; - - @override - Annotation toAst() => new Annotation( + Annotation toAst([Scope scope = const Scope.identity()]) => new Annotation( null, - _stringId(_reference), + scope.getIdentifier(_reference, _importFrom), null, null, null, ); - - @override - Annotation toScopedAst(_) => throw new UnimplementedError(); } diff --git a/lib/src/builders/class_builder.dart b/lib/src/builders/class_builder.dart index e9ca417..9235918 100644 --- a/lib/src/builders/class_builder.dart +++ b/lib/src/builders/class_builder.dart @@ -5,12 +5,23 @@ part of code_builder; /// Builds a [ClassDeclaration] AST. -class ClassBuilder extends _AbstractCodeBuilder { +class ClassBuilder implements CodeBuilder { static Token _abstract = new KeywordToken(Keyword.ABSTRACT, 0); static Token _extends = new KeywordToken(Keyword.EXTENDS, 0); static Token _implements = new KeywordToken(Keyword.IMPLEMENTS, 0); static Token _with = new KeywordToken(Keyword.WITH, 0); + 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 @@ -18,66 +29,72 @@ class ClassBuilder extends _AbstractCodeBuilder { factory ClassBuilder( String name, { bool abstract: false, - String extend, - Iterable implement: const [], - Iterable mixin: const [], - }) { - var astNode = _emptyClassDeclaration()..name = _stringId(name); - if (abstract) { - astNode.abstractKeyword = _abstract; - } - if (extend != null) { - astNode.extendsClause = new ExtendsClause( - _extends, - new TypeName( - _stringId(extend), - null, - )); - } - if (implement.isNotEmpty) { - astNode.implementsClause = new ImplementsClause( - _implements, - implement - .map/**/((i) => new TypeName(_stringId(i), null)) - .toList()); - } - if (mixin.isNotEmpty) { - astNode.withClause = new WithClause( - _with, - mixin - .map/**/((i) => new TypeName(_stringId(i), null)) - .toList()); - } - return new ClassBuilder._(astNode); - } + TypeBuilder extend, + Iterable implement: const [], + Iterable mixin: const [], + }) => + new ClassBuilder._( + name, + abstract, + extend, + implement, + mixin, + ); - ClassBuilder._(ClassDeclaration astNode) : super._(astNode); + ClassBuilder._( + this._name, + this._isAbstract, + this._extend, + this._implement, + this._mixin, + ); /// Adds an annotation [builder] as metadata. void addAnnotation(AnnotationBuilder builder) { - _astNode.metadata.add(builder.toAst()); + _metadata.add(builder); } /// Adds a constructor [builder]. void addConstructor(ConstructorBuilder builder) { - var astNode = builder.toAst(); - if (astNode.returnType == null) { - astNode.returnType = _astNode.name; - } - _astNode.members.add(astNode); + _constructors.add(builder); } /// Adds a field [builder] as a member on the class. - void addField(FieldBuilder builder, {bool static: false}) { - _astNode.members.add(builder.toFieldAst(static: static)); + void addField(FieldBuilder builder) { + _fields.add(builder); } /// Adds a method [builder] as a member on the class. - void addMethod(MethodBuilder builder, {bool static: false}) { - _astNode.members.add(builder.toMethodAst( - static: static, - canBeAbstract: _astNode.abstractKeyword != null, - )); + void addMethod(MethodBuilder builder) { + _methods.add(builder); + } + + @override + ClassDeclaration toAst([Scope scope = const Scope.identity()]) { + var astNode = _emptyClassDeclaration()..name = _stringId(_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 = _stringId(_name))) + ..members + .addAll(_methods.map/**/((m) => m.toMethodAst(scope))); + return astNode; } static ClassDeclaration _emptyClassDeclaration() => new ClassDeclaration( diff --git a/lib/src/builders/constructor_builder.dart b/lib/src/builders/constructor_builder.dart index 0839a05..31758bb 100644 --- a/lib/src/builders/constructor_builder.dart +++ b/lib/src/builders/constructor_builder.dart @@ -6,55 +6,51 @@ part of code_builder; /// Builds a [ConstructorDeclaration] AST. /// -/// Similar to [MethodBuilder] but adds constructor-only features. -/// -/// Use [ConstructorBuilder.initializeFields] to create something like: -/// class Foo { -/// final _one; -/// final _two; -/// -/// Foo(this._one, this._two); -/// } -class ConstructorBuilder extends _AbstractCodeBuilder { +/// Similar to [MethodBuilder] but with constructor-only features. +class ConstructorBuilder implements CodeBuilder { static final Token _const = new KeywordToken(Keyword.CONST, 0); static final Token _this = new KeywordToken(Keyword.THIS, 0); - /// Create a simple [ConstructorBuilder] that initializes class fields. + final bool _isConstant; + final String _name; + final List _parameters = []; + + factory ConstructorBuilder([String name]) { + return new ConstructorBuilder._(false, name); + } + + factory ConstructorBuilder.isConst([String name]) { + return new ConstructorBuilder._(true, name); + } + + ConstructorBuilder._(this._isConstant, this._name); + + /// Lazily adds [parameter]. /// - /// May optionally be [constant] compatible, or have a [name]. - factory ConstructorBuilder.initializeFields( - {bool constant: false, - String name, - Iterable positionalArguments: const [], - Iterable optionalArguments: const [], - Iterable namedArguments: const []}) { - var parameters = []; - for (var a in positionalArguments) { - parameters.add(new ParameterBuilder(a, field: true).toAst()); - } - for (var a in optionalArguments) { - parameters.add(new ParameterBuilder.optional(a, field: true).toAst()); - } - for (var a in namedArguments) { - parameters.add(new ParameterBuilder.named(a, field: true).toAst()); - } + /// When the method is emitted as an AST, [ParameterBuilder.toAst] is used. + void addParameter(ParameterBuilder builder) { + _parameters.add(builder); + } + + @override + ConstructorDeclaration toAst([Scope scope = const Scope.identity()]) { var astNode = new ConstructorDeclaration( null, null, null, null, - constant ? _const : null, + _isConstant ? _const : null, null, null, - name != null ? _stringId(name) : null, - MethodBuilder._emptyParameters()..parameters.addAll(parameters), + _name != null ? _stringId(_name) : null, + MethodBuilder._emptyParameters() + ..parameters.addAll( + _parameters.map/**/((p) => p.toAst(scope))), null, null, null, new EmptyFunctionBody(MethodBuilder._semicolon), ); - return new ConstructorBuilder._(astNode); + return astNode; } - - ConstructorBuilder._(ConstructorDeclaration astNode) : super._(astNode); } diff --git a/lib/src/builders/expression_builder.dart b/lib/src/builders/expression_builder.dart index 365dc65..bc2815d 100644 --- a/lib/src/builders/expression_builder.dart +++ b/lib/src/builders/expression_builder.dart @@ -23,16 +23,16 @@ final Token _openP = new Token(TokenType.OPEN_PAREN, 0); final Token _semicolon = new Token(TokenType.SEMICOLON, 0); // TODO(matanl): Make this part of the public API. See annotation_builder.dart. -ExpressionFunctionBody _asFunctionBody(CodeBuilder expression) { +ExpressionFunctionBody _asFunctionBody(CodeBuilder expression, Scope scope,) { return new ExpressionFunctionBody( null, null, - expression.toAst(), + expression.toAst(scope), _semicolon, ); } -FunctionExpression _asFunctionExpression(CodeBuilder expression) { +FunctionExpression _asFunctionExpression(CodeBuilder expression, Scope scope,) { return new FunctionExpression( null, new FormalParameterList( @@ -42,7 +42,7 @@ FunctionExpression _asFunctionExpression(CodeBuilder expression) { null, _closeP, ), - _asFunctionBody(expression), + _asFunctionBody(expression, scope), ); } @@ -73,6 +73,7 @@ abstract class ExpressionBuilder implements CodeBuilder { /// Optionally specify [positional] and [named] arguments. factory ExpressionBuilder.invoke( String name, { + String importFrom, Iterable> positional: const [], Map> named: const {}, }) { @@ -80,6 +81,21 @@ abstract class ExpressionBuilder implements CodeBuilder { name, new List>.unmodifiable(positional), new Map>.unmodifiable(named), + importFrom, + ); + } + + 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), ); } @@ -93,10 +109,10 @@ abstract class ExpressionBuilder implements CodeBuilder { }); // TODO(matanl): Rename to invoke when factory is removed. /// Returns wrapped as a [ExpressionFunctionBody] AST. - ExpressionFunctionBody toFunctionBody() => _asFunctionBody(this); + ExpressionFunctionBody toFunctionBody([Scope scope = const Scope.identity()]) => _asFunctionBody(this, scope); /// Returns wrapped as a [FunctionExpression] AST. - FunctionExpression toFunctionExpression() => _asFunctionExpression(this); + FunctionExpression toFunctionExpression([Scope scope = const Scope.identity()]) => _asFunctionExpression(this, scope); /// Converts to a [StatementBuilder]. /// @@ -118,7 +134,7 @@ class LiteralBool extends _LiteralExpression { const LiteralBool(this._value); @override - BooleanLiteral toAst() => _value ? _true : _false; + BooleanLiteral toAst([_]) => _value ? _true : _false; } /// Represents an expression value of a literal number. @@ -129,8 +145,10 @@ class LiteralInt extends _LiteralExpression { const LiteralInt(this._value); @override - IntegerLiteral toAst() => - new IntegerLiteral(new StringToken(TokenType.INT, '$_value', 0), _value); + IntegerLiteral toAst([_]) => new IntegerLiteral( + new StringToken(TokenType.INT, '$_value', 0), + _value, + ); } /// Represents an expression value of a literal `'string'`. @@ -141,7 +159,7 @@ class LiteralString extends _LiteralExpression { const LiteralString(this._value); @override - StringLiteral toAst() => new SimpleStringLiteral( + StringLiteral toAst([_]) => new SimpleStringLiteral( new StringToken( TokenType.STRING, "'$_value'", @@ -151,26 +169,41 @@ class LiteralString extends _LiteralExpression { ); } -class _InvokeExpression extends ExpressionBuilder - implements CodeBuilder { +class _InvokeExpression extends ExpressionBuilder { static final Token _colon = new Token(TokenType.COLON, 0); + 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, + super._(); + + const _InvokeExpression.newInstance( + this._type, + this._name, + this._positionalArguments, + this._namedArguments, + ) + : _target = null, + _importFrom = null, super._(); const _InvokeExpression.target( this._name, this._target, this._positionalArguments, this._namedArguments) - : super._(); + : _importFrom = null, + _type = null, + super._(); @override ExpressionBuilder invokeSelf( @@ -181,30 +214,42 @@ class _InvokeExpression extends ExpressionBuilder _invokeSelfImpl(this, name, positional: positional, named: named); @override - InvocationExpression toAst() { + Expression toAst([Scope scope = const Scope.identity()]) { + // TODO(matanl): Move to TypeBuilder.newInstance. + if (_type != null) { + return new InstanceCreationExpression( + new KeywordToken(Keyword.NEW, 0), + new ConstructorName( + _type.toAst(scope), + _name != null ? new Token(TokenType.PERIOD, 0) : null, + _name != null ? _stringId(_name) : null, + ), + _getArgumentList(scope), + ); + } return new MethodInvocation( - _target?.toAst(), + _target?.toAst(scope), _target != null ? new Token(TokenType.PERIOD, 0) : null, _stringId(_name), null, - _getArgumentList(), + _getArgumentList(scope), ); } @override StatementBuilder toStatement() => new StatementBuilder.fromExpression(this); - ArgumentList _getArgumentList() { + ArgumentList _getArgumentList(Scope scope) { return new ArgumentList( new Token(TokenType.OPEN_CURLY_BRACKET, 0), - _positionalArguments.map/* p.toAst()).toList() + _positionalArguments.map/* p.toAst(scope)).toList() ..addAll(_namedArguments.keys .map/**/((name) => new NamedExpression( new Label( _stringId(name), _colon, ), - _namedArguments[name].toAst(), + _namedArguments[name].toAst(scope), ))), new Token(TokenType.CLOSE_CURLY_BRACKET, 0), ); @@ -224,10 +269,10 @@ abstract class _LiteralExpression _invokeSelfImpl(this, name, positional: positional, named: named); @override - ExpressionFunctionBody toFunctionBody() => _asFunctionBody(this); + ExpressionFunctionBody toFunctionBody([Scope scope = const Scope.identity()]) => _asFunctionBody(this, scope); @override - FunctionExpression toFunctionExpression() => _asFunctionExpression(this); + FunctionExpression toFunctionExpression([Scope scope = const Scope.identity()]) => _asFunctionExpression(this, scope); @override StatementBuilder toStatement() => new StatementBuilder.fromExpression(this); @@ -239,5 +284,5 @@ class _LiteralNull extends _LiteralExpression { const _LiteralNull(); @override - NullLiteral toAst() => _null; + NullLiteral toAst([_]) => _null; } diff --git a/lib/src/builders/field_builder.dart b/lib/src/builders/field_builder.dart index c6e9e2c..5b5b355 100644 --- a/lib/src/builders/field_builder.dart +++ b/lib/src/builders/field_builder.dart @@ -19,6 +19,7 @@ class FieldBuilder implements CodeBuilder { final bool _isConst; final bool _isFinal; + final bool _isStatic; final ExpressionBuilder _initialize; final String _name; final TypeBuilder _type; @@ -30,11 +31,13 @@ class FieldBuilder implements CodeBuilder { this._name, { TypeBuilder type, ExpressionBuilder initialize, + bool static: false, }) : this._type = type, this._initialize = initialize, this._isFinal = false, - this._isConst = false; + this._isConst = false, + this._isStatic = static; /// Create a new field builder that emits a `const` field. /// @@ -43,11 +46,13 @@ class FieldBuilder implements CodeBuilder { this._name, { TypeBuilder type, ExpressionBuilder initialize, + bool static: false, }) : this._type = type, this._initialize = initialize, this._isFinal = false, - this._isConst = true; + this._isConst = true, + this._isStatic = false; /// Create a new field builder that emits a `final` field. /// @@ -56,21 +61,13 @@ class FieldBuilder implements CodeBuilder { this._name, { TypeBuilder type, ExpressionBuilder initialize, + bool static: false, }) : this._type = type, this._initialize = initialize, this._isFinal = true, - this._isConst = false; - - Token _getVariableKeyword() { - if (_isFinal) { - return _final; - } - if (_isConst) { - return _const; - } - return _type == null ? _var : null; - } + this._isConst = false, + this._isStatic = static; /// Returns a copy-safe [AstNode] representing the current builder state. /// @@ -79,32 +76,43 @@ class FieldBuilder implements CodeBuilder { /// [toVariablesAst]. @override @visibleForTesting - Declaration toAst() => toFieldAst(); + Declaration toAst([Scope scope = const Scope.identity()]) => + toFieldAst(scope); /// Returns a copy-safe [FieldDeclaration] AST representing current state. - FieldDeclaration toFieldAst({ - bool static: false, - }) => + FieldDeclaration toFieldAst([Scope scope = const Scope.identity()]) => new FieldDeclaration( null, null, - static ? _static : null, - toVariablesAst(), + _isStatic ? _static : null, + toVariablesAst(scope), null, ); /// Returns a copy-safe [VariableDeclaration] AST representing current state. - VariableDeclarationList toVariablesAst() => new VariableDeclarationList( + VariableDeclarationList toVariablesAst( + [Scope scope = const Scope.identity()]) => + new VariableDeclarationList( null, null, _getVariableKeyword(), - _type?.toAst(), + _type?.toAst(scope), [ new VariableDeclaration( _stringId(_name), _initialize != null ? _equals : null, - _initialize?.toAst(), + _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 index d6fdcc7..a4fc370 100644 --- a/lib/src/builders/file_builder.dart +++ b/lib/src/builders/file_builder.dart @@ -5,12 +5,33 @@ part of code_builder; CompilationUnit _emptyCompilationUnit() => new CompilationUnit( - null, - null, - null, - null, - null, - ); + null, + null, + null, + null, + null, + ); + +/// An `export` directive in a [LibraryBuilder]. +class ExportBuilder extends _AbstractCodeBuilder { + /// Create a new `export` directive exporting [uri]. + factory ExportBuilder(String uri) { + var astNode = _createExportDirective()..uri = _stringLit("'$uri'"); + return new ExportBuilder._(astNode); + } + + ExportBuilder._(ExportDirective astNode) : super._(astNode); + + static ExportDirective _createExportDirective() => new ExportDirective( + null, + null, + null, + null, + null, + null, + null, + ); +} /// Builds files of Dart source code. /// @@ -24,6 +45,39 @@ abstract class FileBuilder extends _AbstractCodeBuilder { } } +/// An `import` directive in a [FileBuilder]. +class ImportBuilder extends _AbstractCodeBuilder { + static Token _as = new KeywordToken(Keyword.AS, 0); + + /// Create a new `import` directive importing [uri]. + /// + /// Optionally prefix [as]. + factory ImportBuilder(String uri, {String as}) { + var astNode = _createImportDirective()..uri = _stringLit("'$uri'"); + if (as != null) { + astNode + ..asKeyword = _as + ..prefix = _stringId(as); + } + return new ImportBuilder._(astNode); + } + + ImportBuilder._(ImportDirective astNode) : super._(astNode); + + 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 { static final Token _library = new KeywordToken(Keyword.LIBRARY, 0); @@ -35,13 +89,14 @@ class LibraryBuilder extends FileBuilder { var astNode = _emptyCompilationUnit(); if (name != null) { astNode.directives.add(new LibraryDirective( - null, - null, - _library, - new LibraryIdentifier([_stringId(name)]), - null,)); + null, + null, + _library, + new LibraryIdentifier([_stringId(name)]), + null, + )); } - return new LibraryBuilder._(astNode, new Scope.dedupe()); + return new LibraryBuilder._(astNode, const Scope.identity()); } /// Create a new standalone Dart library, optionally with a [name]. @@ -53,11 +108,12 @@ class LibraryBuilder extends FileBuilder { var astNode = _emptyCompilationUnit(); if (name != null) { astNode.directives.add(new LibraryDirective( - null, - null, - _library, - new LibraryIdentifier([_stringId(name)]), - null,)); + null, + null, + _library, + new LibraryIdentifier([_stringId(name)]), + null, + )); } return new LibraryBuilder._(astNode, scope ?? new Scope()); } @@ -66,11 +122,7 @@ class LibraryBuilder extends FileBuilder { @override void addDeclaration(CodeBuilder declaration) { - if (declaration is ScopeAware) { - _astNode.declarations.add(declaration.toScopedAst(_scope)); - } else { - super.addDeclaration(declaration); - } + _astNode.declarations.add(declaration.toAst(_scope)); } /// Adds [directive]'s resulting AST to the source. @@ -79,7 +131,7 @@ class LibraryBuilder extends FileBuilder { } @override - CompilationUnit toAst() { + CompilationUnit toAst([_]) { var originalAst = super.toAst(); originalAst.directives..addAll(_scope.getImports().map((i) => i.toAst())); return originalAst; @@ -95,68 +147,15 @@ class PartBuilder extends FileBuilder { factory PartBuilder(String name) { var astNode = _emptyCompilationUnit(); astNode.directives.add(new PartOfDirective( - null, - null, - _part, - _of, - new LibraryIdentifier([_stringId(name)]), - null, - )); - return new PartBuilder._(astNode); - } - - PartBuilder._(CompilationUnit astNode) : super._(astNode); -} -/// An `export` directive in a [LibraryBuilder]. -class ExportBuilder extends _AbstractCodeBuilder { - /// Create a new `export` directive exporting [uri]. - factory ExportBuilder(String uri) { - var astNode = _createExportDirective()..uri = _stringLit("'$uri'"); - return new ExportBuilder._(astNode); - } - - ExportBuilder._(ExportDirective astNode) : super._(astNode); - - static ExportDirective _createExportDirective() => new ExportDirective( - null, - null, - null, null, null, + _part, + _of, + new LibraryIdentifier([_stringId(name)]), null, - null, - ); -} - -/// An `import` directive in a [FileBuilder]. -class ImportBuilder extends _AbstractCodeBuilder { - static Token _as = new KeywordToken(Keyword.AS, 0); - - /// Create a new `import` directive importing [uri]. - /// - /// Optionally prefix [as]. - factory ImportBuilder(String uri, {String as}) { - var astNode = _createImportDirective()..uri = _stringLit("'$uri'"); - if (as != null) { - astNode - ..asKeyword = _as - ..prefix = _stringId(as); - } - return new ImportBuilder._(astNode); + )); + return new PartBuilder._(astNode); } - ImportBuilder._(ImportDirective astNode) : super._(astNode); - - static ImportDirective _createImportDirective() => new ImportDirective( - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ); + PartBuilder._(CompilationUnit astNode) : super._(astNode); } diff --git a/lib/src/builders/method_builder.dart b/lib/src/builders/method_builder.dart index f43b756..dae53d5 100644 --- a/lib/src/builders/method_builder.dart +++ b/lib/src/builders/method_builder.dart @@ -11,9 +11,7 @@ part of code_builder; /// the top level and within other methods) via [toFunctionAst]. /// /// To return nothing (`void`), use [MethodBuilder.returnVoid]. -class MethodBuilder implements - CodeBuilder, - ScopeAware { +class MethodBuilder implements CodeBuilder { static Token _abstract = new KeywordToken(Keyword.ABSTRACT, 0); static Token _semicolon = new Token(TokenType.SEMICOLON, 0); static Token _static = new KeywordToken(Keyword.STATIC, 0); @@ -26,25 +24,39 @@ class MethodBuilder implements 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}) { - return new MethodBuilder._(name, returns); + 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]) { - return new MethodBuilder._(name, _typeVoid); + factory MethodBuilder.returnVoid({ + String name, + bool abstract: false, + bool static: false, + }) { + return new MethodBuilder._(name, _typeVoid, static, abstract); } - MethodBuilder._(this._name, this._returnType); - - @override - List get requiredImports => null; + MethodBuilder._( + this._name, + this._returnType, + this._isStatic, + this._isAbstract, + ); /// Lazily adds [annotation]. /// @@ -81,63 +93,57 @@ class MethodBuilder implements /// [toMethodAst]. @override @visibleForTesting - Declaration toAst() => toScopedAst(const Scope.identity()); + Declaration toAst([Scope scope = const Scope.identity()]) => + toFunctionAst(scope); /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - FunctionDeclaration toFunctionAst({Scope scope: const Scope.identity()}) { + FunctionDeclaration toFunctionAst([Scope scope = const Scope.identity()]) { var functionAst = _emptyFunction() - ..metadata.addAll(_annotations.map/**/((a) => a.toAst())) + ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) ..name = _stringId(_name) - ..returnType = _returnType?.toScopedAst(scope); + ..returnType = _returnType?.toAst(scope); if (_returnExpression != null) { functionAst.functionExpression = _returnExpression.toFunctionExpression(); } else { functionAst.functionExpression = new FunctionExpression( null, _emptyParameters(), - _blockBody(_statements.map/**/((s) => s.toAst())), + _blockBody(_statements.map/**/((s) => s.toAst(scope))), ); } if (_parameters.isNotEmpty) { functionAst.functionExpression.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toScopedAst(scope))); + .addAll(_parameters.map/**/((p) => p.toAst(scope))); } return functionAst; } /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - MethodDeclaration toMethodAst({ - bool static: false, - bool canBeAbstract: false, - Scope scope: const Scope.identity(), - }) { + MethodDeclaration toMethodAst([Scope scope = const Scope.identity()]) { var methodAst = _emptyMethod() - ..metadata.addAll(_annotations.map/**/((a) => a.toAst())) + ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) ..name = _stringId(_name) - ..returnType = _returnType?.toScopedAst(scope); - FunctionBody methodBody = _returnExpression?.toFunctionBody(); - if (static) { + ..returnType = _returnType?.toAst(scope); + FunctionBody methodBody = _returnExpression?.toFunctionBody(scope); + if (_isStatic) { methodAst.modifierKeyword = _static; if (methodBody == null) { methodBody = _blockBody(); } } if (methodBody == null) { - methodBody = canBeAbstract + methodBody = _isAbstract ? new EmptyFunctionBody(_semicolon) - : _blockBody(_statements.map/**/((s) => s.toAst())); + : _blockBody(_statements.map/**/((s) => s.toAst(scope))); } if (_parameters.isNotEmpty) { methodAst.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toScopedAst(scope))); + .addAll(_parameters.map/**/((p) => p.toAst(scope))); } methodAst.body = methodBody; return methodAst; } - @override - Declaration toScopedAst(Scope scope) => toFunctionAst(scope: scope); - @override String toString() => 'MethodBuilder ${toAst().toSource()}'; diff --git a/lib/src/builders/parameter_builder.dart b/lib/src/builders/parameter_builder.dart index 16466f6..6079fab 100644 --- a/lib/src/builders/parameter_builder.dart +++ b/lib/src/builders/parameter_builder.dart @@ -9,7 +9,7 @@ part of code_builder; /// 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 extends ScopeAware { +class ParameterBuilder implements CodeBuilder { static final Token _this = new KeywordToken(Keyword.THIS, 0); final String _name; @@ -104,7 +104,7 @@ class ParameterBuilder extends ScopeAware { } @override - FormalParameter toScopedAst(Scope scope) { + FormalParameter toAst([Scope scope = const Scope.identity()]) { FormalParameter astNode; if (_isField) { astNode = _createFieldFormalParameter(scope); @@ -123,28 +123,12 @@ class ParameterBuilder extends ScopeAware { return astNode; } - static DefaultFormalParameter _createDefaultFormalParameter( - FormalParameter parameter, - bool named, - ExpressionBuilder defaultTo, - Scope scope, - ) { - return new DefaultFormalParameter( - parameter, - named ? ParameterKind.NAMED : ParameterKind.POSITIONAL, - defaultTo != null - ? named ? new Token(TokenType.COLON, 0) : new Token(TokenType.EQ, 0) - : null, - defaultTo?.toAst(), - ); - } - FieldFormalParameter _createFieldFormalParameter(Scope scope) => new FieldFormalParameter( null, null, null, - _type?.toScopedAst(scope), + _type?.toAst(scope), _this, null, _stringId(_name), @@ -157,7 +141,23 @@ class ParameterBuilder extends ScopeAware { null, null, null, - _type?.toScopedAst(scope), + _type?.toAst(scope), _stringId(_name), ); + + static DefaultFormalParameter _createDefaultFormalParameter( + FormalParameter parameter, + bool named, + ExpressionBuilder defaultTo, + Scope scope, + ) { + return new DefaultFormalParameter( + parameter, + named ? ParameterKind.NAMED : ParameterKind.POSITIONAL, + defaultTo != null + ? named ? new Token(TokenType.COLON, 0) : new Token(TokenType.EQ, 0) + : null, + defaultTo?.toAst(), + ); + } } diff --git a/lib/src/builders/type_builder.dart b/lib/src/builders/type_builder.dart index 19025c8..cff6f2f 100644 --- a/lib/src/builders/type_builder.dart +++ b/lib/src/builders/type_builder.dart @@ -5,7 +5,7 @@ part of code_builder; /// Build a [TypeName] AST. -class TypeBuilder implements ScopeAware { +class TypeBuilder implements CodeBuilder { final String _identifier; final String _importFrom; @@ -19,13 +19,7 @@ class TypeBuilder implements ScopeAware { : _importFrom = importFrom; @override - List get requiredImports => [_importFrom]; - - @override - TypeName toAst() => toScopedAst(const Scope.identity()); - - @override - TypeName toScopedAst(Scope scope) { + TypeName toAst([Scope scope = const Scope.identity()]) { return new TypeName(scope.getIdentifier(_identifier, _importFrom), null); } } diff --git a/lib/testing/equals_source.dart b/lib/testing/equals_source.dart index 5c69e4f..1231cb0 100644 --- a/lib/testing/equals_source.dart +++ b/lib/testing/equals_source.dart @@ -8,6 +8,13 @@ import 'package:analyzer/src/dart/ast/token.dart'; import 'package:code_builder/code_builder.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 @@ -19,7 +26,11 @@ import 'package:matcher/matcher.dart'; /// /// If you have code that is not consider a valid compilation unit (like an /// expression, you should flip [format] to `false`). -Matcher equalsSource(String source, {bool format: true, Scope scope}) { +Matcher equalsSource( + String source, { + bool format: true, + Scope scope: const Scope.identity(), +}) { return new _EqualsSource( scope, format ? dartfmt(source) : source, @@ -31,35 +42,6 @@ Identifier _stringId(String s) { return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0)); } -/// 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(); - -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), - new Token(TokenType.PERIOD, 0), - _stringId(symbol), - ); - } - - @override - Iterable getImports() => const []; -} - -// 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 _EqualsSource extends Matcher { final Scope _scope; final String _source; @@ -80,9 +62,11 @@ class _EqualsSource extends Matcher { bool verbose, ) { if (item is CodeBuilder) { + var origin = _formatAst(item); + print(origin); return equals(_source).describeMismatch( - _formatAst(item), - mismatchDescription, + origin, + mismatchDescription.addDescriptionOf(origin), matchState, verbose, ); @@ -91,25 +75,42 @@ class _EqualsSource extends Matcher { } } - String _formatAst(CodeBuilder builder) { - AstNode astNode; - if (_scope != null && builder is ScopeAware) { - astNode = builder.toScopedAst(_scope); - } else { - astNode = builder.toAst(); + @override + bool matches(item, _) { + if (item is CodeBuilder) { + return _formatAst(item) == _source; } + return false; + } + + String _formatAst(CodeBuilder builder) { + var astNode = builder.toAst(_scope); if (_isFormatted) { return prettyToSource(astNode); } else { return astNode.toSource(); } } +} + +// 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 - bool matches(item, _) { - if (item is CodeBuilder) { - return _formatAst(item) == _source; - } - return false; + Identifier getIdentifier(String symbol, String importUri) { + var fileWithoutExt = + Uri.parse(importUri).pathSegments.last.split('.').first; + return new PrefixedIdentifier( + _stringId(fileWithoutExt), + new Token(TokenType.PERIOD, 0), + _stringId(symbol), + ); } + + @override + Iterable getImports() => const []; } diff --git a/pubspec.yaml b/pubspec.yaml index d3f7b32..ee334a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 0.1.0-dev+2 +version: 0.1.1-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/class_builder_test.dart b/test/builders/class_builder_test.dart index ac4cf91..2d3bffd 100644 --- a/test/builders/class_builder_test.dart +++ b/test/builders/class_builder_test.dart @@ -21,21 +21,25 @@ void main() { test('should emit a class extending another class', () { expect( - new ClassBuilder('Animal', extend: 'Organism'), + 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: ['Delicious']), + 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: 'Organism', mixin: ['Breathing']), + new ClassBuilder( + 'Animal', + extend: new TypeBuilder('Organism'), + mixin: [new TypeBuilder('Breathing')], + ), equalsSource('class Animal extends Organism with Breathing {}'), ); }); @@ -59,7 +63,7 @@ void main() { expect( new ClassBuilder('Animal', abstract: true) ..addMethod( - new MethodBuilder.returnVoid('eat'), + new MethodBuilder.returnVoid(name: 'eat', abstract: true), ), equalsSource(r''' abstract class Animal { @@ -75,9 +79,9 @@ void main() { ..addMethod( new MethodBuilder( name: 'create', + static: true, returns: new TypeBuilder('Animal'), )..setExpression(literalNull), - static: true, ), equalsSource(r''' class Animal { @@ -116,7 +120,7 @@ void main() { }); test('default constructor', () { - clazz.addConstructor(new ConstructorBuilder.initializeFields()); + clazz.addConstructor(new ConstructorBuilder()); expect( clazz, equalsSource( @@ -129,8 +133,7 @@ void main() { }); test('default const constructor', () { - clazz.addConstructor( - new ConstructorBuilder.initializeFields(constant: true)); + clazz.addConstructor(new ConstructorBuilder.isConst()); expect( clazz, equalsSource( @@ -143,10 +146,13 @@ void main() { }); test('initializing fields', () { - clazz.addConstructor(new ConstructorBuilder.initializeFields( - positionalArguments: ['a'], - optionalArguments: ['b'], - )); + clazz.addConstructor(new ConstructorBuilder() + ..addParameter( + new ParameterBuilder('a', field: true), + ) + ..addParameter( + new ParameterBuilder.optional('b', field: true), + )); expect( clazz, equalsSource( diff --git a/test/builders/file_builder_test.dart b/test/builders/file_builder_test.dart index 98e2136..a38092f 100644 --- a/test/builders/file_builder_test.dart +++ b/test/builders/file_builder_test.dart @@ -12,8 +12,8 @@ void main() { }); test('should emit a file with a library directive', () { - expect( - new LibraryBuilder('code_builder'), equalsSource('library code_builder;')); + expect(new LibraryBuilder('code_builder'), + equalsSource('library code_builder;')); }); test('should emit a file with a part of directive', () { diff --git a/test/builders/method_builder_test.dart b/test/builders/method_builder_test.dart index e569657..7f6f348 100644 --- a/test/builders/method_builder_test.dart +++ b/test/builders/method_builder_test.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart'; void main() { test('should emit a simple "void" method', () { expect( - new MethodBuilder.returnVoid('main'), + new MethodBuilder.returnVoid(name: 'main'), equalsSource('void main() {}'), ); }); @@ -27,10 +27,12 @@ void main() { 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'), - )), + ) + ..addParameter(new ParameterBuilder( + 'context', + type: + new TypeBuilder('Context', importFrom: 'package:bar/bar.dart'), + )), equalsSource( r''' foo.Foo toFoo(bar.Context context) {} @@ -117,7 +119,7 @@ void main() { group('with statements', () { test('should work with an expression', () { expect( - new MethodBuilder.returnVoid('main') + new MethodBuilder.returnVoid(name: 'main') ..addStatement(const LiteralString('Hello World').toStatement()), equalsSource(r''' void main() { @@ -129,7 +131,7 @@ void main() { test('should work invoking an expression', () { expect( - new MethodBuilder.returnVoid('main') + new MethodBuilder.returnVoid(name: 'main') ..addStatement( new ExpressionBuilder.invoke('print', positional: [ const LiteralString('Hello World').invokeSelf( @@ -151,7 +153,7 @@ void main() { MethodBuilder method; setUp(() { - method = new MethodBuilder.returnVoid('main'); + method = new MethodBuilder.returnVoid(name: 'main'); }); test('single required parameter', () { diff --git a/test/integration_test.dart b/test/integration_test.dart index 4571d66..2ecbe1a 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -10,29 +10,84 @@ 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: ['App']) + 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'), - )) - ..addConstructor(new ConstructorBuilder.initializeFields( - positionalArguments: ['_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'), + returns: const TypeBuilder( + 'Thing', + importFrom: 'package:app/thing.dart', + ), ) ..addAnnotation(atOverride) - ..setExpression(new ExpressionBuilder.invoke( - 'new Thing', + ..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() + var lib = new LibraryBuilder.scope() ..addDirective( new ImportBuilder('app.dart'), ) @@ -42,14 +97,18 @@ void main() { equalsSource( r''' import 'app.dart'; + import 'package:app/app.dart' as _i1; + import 'dart:core' as _i2; + import 'package:app/thing.dart' as _i3; - class Injector implements App { - final Module _module; + + class Injector implements _i1.App { + final _i1.Module _module; Injector(this._module); - @override - Thing getThing() => new Thing(_module.getDep1(), _module.getDep2()); + @_i2.override + _i3.Thing getThing() => new _i3.Thing(_module.getDep1(), _module.getDep2()); } ''', ), diff --git a/test/scope_test.dart b/test/scope_test.dart index 3f45a2f..1d16b9e 100644 --- a/test/scope_test.dart +++ b/test/scope_test.dart @@ -9,17 +9,20 @@ void main() { setUp(() => scope = const Scope.identity()); test('should do nothing', () { - var identifiers = [ + 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( + identifiers, + [ + 'Foo', + 'Bar', + 'Baz', + ], + ); expect(scope.getImports(), isEmpty); }); @@ -31,22 +34,28 @@ void main() { setUp(() => scope = new Scope.dedupe()); test('should just output non-prefixed and de-duplicate imports', () { - var identifiers = [ + 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( + identifiers, + [ + 'Foo', + 'Bar', + 'Baz', + ], + ); - expect(scope.getImports().map/**/((i) => i.toAst().toSource()), [ - r"import 'package:foo/foo.dart';", - r"import 'package:baz/baz.dart';", - ],); + expect( + scope.getImports().map/**/((i) => i.toAst().toSource()), + [ + r"import 'package:foo/foo.dart';", + r"import 'package:baz/baz.dart';", + ], + ); }); }); @@ -56,22 +65,28 @@ void main() { setUp(() => scope = new Scope()); test('should out prefixed with a counter', () { - var identifiers = [ + 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( + 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;", - ],); + expect( + scope.getImports().map/**/((i) => i.toAst().toSource()), + [ + r"import 'package:foo/foo.dart' as _i1;", + r"import 'package:baz/baz.dart' as _i2;", + ], + ); }); }); } From 787128974dd3563b79cd2a5e922894aee0a6bc4e Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Fri, 16 Sep 2016 10:52:30 -0700 Subject: [PATCH 2/2] Address comments. --- lib/code_builder.dart | 6 ++--- lib/src/builders/class_builder.dart | 23 +++++++++++----- lib/src/builders/constructor_builder.dart | 2 +- lib/src/builders/expression_builder.dart | 32 ++++++++++++++++------- lib/src/builders/field_builder.dart | 12 ++++----- lib/src/builders/file_builder.dart | 12 ++++----- lib/src/builders/method_builder.dart | 4 +-- lib/src/builders/parameter_builder.dart | 4 +-- lib/src/scope.dart | 8 +++--- test/builders/class_builder_test.dart | 4 +-- test/integration_test.dart | 5 +++- 11 files changed, 70 insertions(+), 42 deletions(-) diff --git a/lib/code_builder.dart b/lib/code_builder.dart index b4f3c75..ab48ab7 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -54,16 +54,16 @@ final DartFormatter _dartfmt = new DartFormatter(); @visibleForTesting String dartfmt(String source) => _dartfmt.format(source); -// Creates a defensive copy of an AST node. +// Creates a deep copy of an AST node. AstNode/*=E*/ _cloneAst/**/(AstNode/*=E*/ astNode) { return new AstCloner().cloneNode/**/(astNode); } -Identifier _stringId(String s) { +Identifier _stringIdentifier(String s) { return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0)); } -Literal _stringLit(String s) { +Literal _stringLiteral(String s) { return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s); } diff --git a/lib/src/builders/class_builder.dart b/lib/src/builders/class_builder.dart index 9235918..afb3cbc 100644 --- a/lib/src/builders/class_builder.dart +++ b/lib/src/builders/class_builder.dart @@ -28,17 +28,28 @@ class ClassBuilder implements CodeBuilder { /// [implement] or [mixin]. You may also define a `class` as [abstract]. factory ClassBuilder( String name, { - bool abstract: false, TypeBuilder extend, Iterable implement: const [], Iterable mixin: const [], }) => new ClassBuilder._( name, - abstract, + false, extend, - implement, - mixin, + new List.unmodifiable(implement), + new List.unmodifiable(mixin), + ); + + 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._( @@ -71,7 +82,7 @@ class ClassBuilder implements CodeBuilder { @override ClassDeclaration toAst([Scope scope = const Scope.identity()]) { - var astNode = _emptyClassDeclaration()..name = _stringId(_name); + var astNode = _emptyClassDeclaration()..name = _stringIdentifier(_name); if (_isAbstract) { astNode.abstractKeyword = _abstract; } @@ -91,7 +102,7 @@ class ClassBuilder implements CodeBuilder { astNode ..members.addAll(_fields.map/**/((f) => f.toFieldAst(scope))) ..members.addAll(_constructors.map/**/( - (c) => c.toAst(scope)..returnType = _stringId(_name))) + (c) => c.toAst(scope)..returnType = _stringIdentifier(_name))) ..members .addAll(_methods.map/**/((m) => m.toMethodAst(scope))); return astNode; diff --git a/lib/src/builders/constructor_builder.dart b/lib/src/builders/constructor_builder.dart index 31758bb..cf01588 100644 --- a/lib/src/builders/constructor_builder.dart +++ b/lib/src/builders/constructor_builder.dart @@ -42,7 +42,7 @@ class ConstructorBuilder implements CodeBuilder { _isConstant ? _const : null, null, null, - _name != null ? _stringId(_name) : null, + _name != null ? _stringIdentifier(_name) : null, MethodBuilder._emptyParameters() ..parameters.addAll( _parameters.map/**/((p) => p.toAst(scope))), diff --git a/lib/src/builders/expression_builder.dart b/lib/src/builders/expression_builder.dart index bc2815d..823e004 100644 --- a/lib/src/builders/expression_builder.dart +++ b/lib/src/builders/expression_builder.dart @@ -23,7 +23,10 @@ final Token _openP = new Token(TokenType.OPEN_PAREN, 0); final Token _semicolon = new Token(TokenType.SEMICOLON, 0); // TODO(matanl): Make this part of the public API. See annotation_builder.dart. -ExpressionFunctionBody _asFunctionBody(CodeBuilder expression, Scope scope,) { +ExpressionFunctionBody _asFunctionBody( + CodeBuilder expression, + Scope scope, +) { return new ExpressionFunctionBody( null, null, @@ -32,7 +35,10 @@ ExpressionFunctionBody _asFunctionBody(CodeBuilder expression, Scope ); } -FunctionExpression _asFunctionExpression(CodeBuilder expression, Scope scope,) { +FunctionExpression _asFunctionExpression( + CodeBuilder expression, + Scope scope, +) { return new FunctionExpression( null, new FormalParameterList( @@ -109,10 +115,14 @@ abstract class ExpressionBuilder implements CodeBuilder { }); // TODO(matanl): Rename to invoke when factory is removed. /// Returns wrapped as a [ExpressionFunctionBody] AST. - ExpressionFunctionBody toFunctionBody([Scope scope = const Scope.identity()]) => _asFunctionBody(this, scope); + ExpressionFunctionBody toFunctionBody( + [Scope scope = const Scope.identity()]) => + _asFunctionBody(this, scope); /// Returns wrapped as a [FunctionExpression] AST. - FunctionExpression toFunctionExpression([Scope scope = const Scope.identity()]) => _asFunctionExpression(this, scope); + FunctionExpression toFunctionExpression( + [Scope scope = const Scope.identity()]) => + _asFunctionExpression(this, scope); /// Converts to a [StatementBuilder]. /// @@ -222,7 +232,7 @@ class _InvokeExpression extends ExpressionBuilder { new ConstructorName( _type.toAst(scope), _name != null ? new Token(TokenType.PERIOD, 0) : null, - _name != null ? _stringId(_name) : null, + _name != null ? _stringIdentifier(_name) : null, ), _getArgumentList(scope), ); @@ -230,7 +240,7 @@ class _InvokeExpression extends ExpressionBuilder { return new MethodInvocation( _target?.toAst(scope), _target != null ? new Token(TokenType.PERIOD, 0) : null, - _stringId(_name), + _stringIdentifier(_name), null, _getArgumentList(scope), ); @@ -246,7 +256,7 @@ class _InvokeExpression extends ExpressionBuilder { ..addAll(_namedArguments.keys .map/**/((name) => new NamedExpression( new Label( - _stringId(name), + _stringIdentifier(name), _colon, ), _namedArguments[name].toAst(scope), @@ -269,10 +279,14 @@ abstract class _LiteralExpression _invokeSelfImpl(this, name, positional: positional, named: named); @override - ExpressionFunctionBody toFunctionBody([Scope scope = const Scope.identity()]) => _asFunctionBody(this, scope); + ExpressionFunctionBody toFunctionBody( + [Scope scope = const Scope.identity()]) => + _asFunctionBody(this, scope); @override - FunctionExpression toFunctionExpression([Scope scope = const Scope.identity()]) => _asFunctionExpression(this, scope); + FunctionExpression toFunctionExpression( + [Scope scope = const Scope.identity()]) => + _asFunctionExpression(this, scope); @override StatementBuilder toStatement() => new StatementBuilder.fromExpression(this); diff --git a/lib/src/builders/field_builder.dart b/lib/src/builders/field_builder.dart index 5b5b355..5977e55 100644 --- a/lib/src/builders/field_builder.dart +++ b/lib/src/builders/field_builder.dart @@ -31,13 +31,13 @@ class FieldBuilder implements CodeBuilder { this._name, { TypeBuilder type, ExpressionBuilder initialize, - bool static: false, + bool asStatic: false, }) : this._type = type, this._initialize = initialize, this._isFinal = false, this._isConst = false, - this._isStatic = static; + this._isStatic = asStatic; /// Create a new field builder that emits a `const` field. /// @@ -46,7 +46,7 @@ class FieldBuilder implements CodeBuilder { this._name, { TypeBuilder type, ExpressionBuilder initialize, - bool static: false, + bool asStatic: false, }) : this._type = type, this._initialize = initialize, @@ -61,13 +61,13 @@ class FieldBuilder implements CodeBuilder { this._name, { TypeBuilder type, ExpressionBuilder initialize, - bool static: false, + bool asStatic: false, }) : this._type = type, this._initialize = initialize, this._isFinal = true, this._isConst = false, - this._isStatic = static; + this._isStatic = asStatic; /// Returns a copy-safe [AstNode] representing the current builder state. /// @@ -99,7 +99,7 @@ class FieldBuilder implements CodeBuilder { _type?.toAst(scope), [ new VariableDeclaration( - _stringId(_name), + _stringIdentifier(_name), _initialize != null ? _equals : null, _initialize?.toAst(scope), ) diff --git a/lib/src/builders/file_builder.dart b/lib/src/builders/file_builder.dart index a4fc370..8e3c026 100644 --- a/lib/src/builders/file_builder.dart +++ b/lib/src/builders/file_builder.dart @@ -16,7 +16,7 @@ CompilationUnit _emptyCompilationUnit() => new CompilationUnit( class ExportBuilder extends _AbstractCodeBuilder { /// Create a new `export` directive exporting [uri]. factory ExportBuilder(String uri) { - var astNode = _createExportDirective()..uri = _stringLit("'$uri'"); + var astNode = _createExportDirective()..uri = _stringLiteral("'$uri'"); return new ExportBuilder._(astNode); } @@ -53,11 +53,11 @@ class ImportBuilder extends _AbstractCodeBuilder { /// /// Optionally prefix [as]. factory ImportBuilder(String uri, {String as}) { - var astNode = _createImportDirective()..uri = _stringLit("'$uri'"); + var astNode = _createImportDirective()..uri = _stringLiteral("'$uri'"); if (as != null) { astNode ..asKeyword = _as - ..prefix = _stringId(as); + ..prefix = _stringIdentifier(as); } return new ImportBuilder._(astNode); } @@ -92,7 +92,7 @@ class LibraryBuilder extends FileBuilder { null, null, _library, - new LibraryIdentifier([_stringId(name)]), + new LibraryIdentifier([_stringIdentifier(name)]), null, )); } @@ -111,7 +111,7 @@ class LibraryBuilder extends FileBuilder { null, null, _library, - new LibraryIdentifier([_stringId(name)]), + new LibraryIdentifier([_stringIdentifier(name)]), null, )); } @@ -151,7 +151,7 @@ class PartBuilder extends FileBuilder { null, _part, _of, - new LibraryIdentifier([_stringId(name)]), + new LibraryIdentifier([_stringIdentifier(name)]), null, )); return new PartBuilder._(astNode); diff --git a/lib/src/builders/method_builder.dart b/lib/src/builders/method_builder.dart index dae53d5..33a6fdf 100644 --- a/lib/src/builders/method_builder.dart +++ b/lib/src/builders/method_builder.dart @@ -100,7 +100,7 @@ class MethodBuilder implements CodeBuilder { FunctionDeclaration toFunctionAst([Scope scope = const Scope.identity()]) { var functionAst = _emptyFunction() ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) - ..name = _stringId(_name) + ..name = _stringIdentifier(_name) ..returnType = _returnType?.toAst(scope); if (_returnExpression != null) { functionAst.functionExpression = _returnExpression.toFunctionExpression(); @@ -122,7 +122,7 @@ class MethodBuilder implements CodeBuilder { MethodDeclaration toMethodAst([Scope scope = const Scope.identity()]) { var methodAst = _emptyMethod() ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) - ..name = _stringId(_name) + ..name = _stringIdentifier(_name) ..returnType = _returnType?.toAst(scope); FunctionBody methodBody = _returnExpression?.toFunctionBody(scope); if (_isStatic) { diff --git a/lib/src/builders/parameter_builder.dart b/lib/src/builders/parameter_builder.dart index 6079fab..886ca24 100644 --- a/lib/src/builders/parameter_builder.dart +++ b/lib/src/builders/parameter_builder.dart @@ -131,7 +131,7 @@ class ParameterBuilder implements CodeBuilder { _type?.toAst(scope), _this, null, - _stringId(_name), + _stringIdentifier(_name), null, null, ); @@ -142,7 +142,7 @@ class ParameterBuilder implements CodeBuilder { null, null, _type?.toAst(scope), - _stringId(_name), + _stringIdentifier(_name), ); static DefaultFormalParameter _createDefaultFormalParameter( diff --git a/lib/src/scope.dart b/lib/src/scope.dart index a96f326..734ec7d 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -39,7 +39,7 @@ class _DeduplicatingScope implements Scope { @override Identifier getIdentifier(String symbol, String import) { _imports.add(import); - return _stringId(symbol); + return _stringIdentifier(symbol); } @override @@ -52,7 +52,7 @@ class _IdentityScope implements Scope { const _IdentityScope(); @override - Identifier getIdentifier(String symbol, _) => _stringId(symbol); + Identifier getIdentifier(String symbol, _) => _stringIdentifier(symbol); @override Iterable getImports() => const []; @@ -66,8 +66,8 @@ class _IncrementingScope implements Scope { @override Identifier getIdentifier(String symbol, String import) { var newId = _imports.putIfAbsent(import, () => ++_counter); - return new PrefixedIdentifier(_stringId('_i$newId'), - new Token(TokenType.PERIOD, 0), _stringId(symbol)); + return new PrefixedIdentifier(_stringIdentifier('_i$newId'), + new Token(TokenType.PERIOD, 0), _stringIdentifier(symbol)); } @override diff --git a/test/builders/class_builder_test.dart b/test/builders/class_builder_test.dart index 2d3bffd..e20cb54 100644 --- a/test/builders/class_builder_test.dart +++ b/test/builders/class_builder_test.dart @@ -14,7 +14,7 @@ void main() { test('should emit an abstract class', () { expect( - new ClassBuilder('Animal', abstract: true), + new ClassBuilder.asAbstract('Animal'), equalsSource('abstract class Animal {}'), ); }); @@ -61,7 +61,7 @@ void main() { test('should emit an abstract class with an abstract method', () { expect( - new ClassBuilder('Animal', abstract: true) + new ClassBuilder.asAbstract('Animal') ..addMethod( new MethodBuilder.returnVoid(name: 'eat', abstract: true), ), diff --git a/test/integration_test.dart b/test/integration_test.dart index 2ecbe1a..1791796 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -63,7 +63,10 @@ void main() { ) ..addField(new FieldBuilder.isFinal( '_module', - type: const TypeBuilder('Module', importFrom: 'package:app/app.dart',), + type: const TypeBuilder( + 'Module', + importFrom: 'package:app/app.dart', + ), )) ..addConstructor(new ConstructorBuilder() ..addParameter(new ParameterBuilder('_module', field: true)))