diff --git a/CHANGELOG.md b/CHANGELOG.md index b98c0d2..dfecf2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.0.0-beta+4 + +- Renamed `PartBuilder` to `PartOfBuilder`. +- Added a new class, `PartBuilder`, to represent `part '...dart'` directives. +- Added the `HasAnnotations` interface to all library/part/directive builders. +- Added `asFactory` and `asConst` to `ConstructorBuilder`. +- Added `ConstructorBuilder.redirectTo` for a redirecting factory constructor. +- Added a `name` getter to `ReferenceBuilder`. +- Supplying an empty constructor name (`''`) is equivalent to `null` (default). +- Automatically encodes string literals with multiple lines as `'''`. + ## 1.0.0-beta+3 - Added support for `genericTypes` parameter for `ExpressionBuilder#invoke`: diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 4a3f6d4..4432924 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -10,7 +10,12 @@ export 'src/builders/expression.dart' export 'src/builders/field.dart' show varConst, varField, varFinal, FieldBuilder; export 'src/builders/file.dart' - show ExportBuilder, ImportBuilder, LibraryBuilder, PartBuilder; + show + ExportBuilder, + ImportBuilder, + LibraryBuilder, + PartBuilder, + PartOfBuilder; export 'src/builders/method.dart' show constructor, diff --git a/lib/src/builders/file.dart b/lib/src/builders/file.dart index cc93d54..52b52a5 100644 --- a/lib/src/builders/file.dart +++ b/lib/src/builders/file.dart @@ -4,13 +4,14 @@ import 'package:analyzer/analyzer.dart'; import 'package:analyzer/dart/ast/standard_ast_factory.dart'; +import 'package:code_builder/src/builders/annotation.dart'; import 'package:code_builder/src/builders/shared.dart'; import 'package:code_builder/src/builders/statement.dart'; import 'package:code_builder/src/tokens.dart'; /// Builds a file of Dart source code. /// -/// See [LibraryBuilder] and [PartBuilder] for concrete implementations. +/// See [LibraryBuilder] and [PartOfBuilder] for concrete implementations. abstract class FileBuilder implements AstBuilder { final List _members = []; @@ -28,8 +29,9 @@ abstract class FileBuilder implements AstBuilder { } /// Builds a standalone file (library) of Dart source code. -class LibraryBuilder extends FileBuilder { +class LibraryBuilder extends FileBuilder with HasAnnotationsMixin { final List> _directives = >[]; + final String _name; final Scope _scope; /// Creates a new standalone Dart library, optionally with [name]. @@ -43,11 +45,7 @@ class LibraryBuilder extends FileBuilder { return new LibraryBuilder._(name, scope ?? new Scope()); } - LibraryBuilder._(String name, this._scope) : super._() { - if (name != null) { - _directives.add(new _LibraryDirectiveBuilder(name)); - } - } + LibraryBuilder._(this._name, this._scope) : super._(); /// Adds a file [directive]. void addDirective(AstBuilder directive) { @@ -60,14 +58,18 @@ class LibraryBuilder extends FileBuilder { } @override - CompilationUnit buildAst([_]) { + CompilationUnit buildAst([Scope scope]) { var members = _members.map((m) { if (m is TopLevelMixin) { return (m as TopLevelMixin).buildTopLevelAst(_scope); } return m.buildAst(_scope); }).toList(); - var directives = [] + var directives = []; + if (_name != null) { + directives.add(new _LibraryDirectiveBuilder(_name, this).buildAst(scope)); + } + directives ..addAll(_scope.toImports().map((d) => d.buildAst())) ..addAll(_directives.map((d) => d.buildAst())); return astFactory.compilationUnit( @@ -81,28 +83,30 @@ class LibraryBuilder extends FileBuilder { } /// Lazily builds a partial file (part of) Dart source code. -class PartBuilder extends FileBuilder { - final String _name; +class PartOfBuilder extends FileBuilder with HasAnnotationsMixin { + final String _uri; + final Scope _scope; /// Creates a partial Dart file. - factory PartBuilder(String name) = PartBuilder._; + factory PartOfBuilder(String uri, [Scope scope]) = PartOfBuilder._; - PartBuilder._(this._name) : super._(); + PartOfBuilder._(this._uri, [this._scope]) : super._(); @override - CompilationUnit buildAst([_]) { + CompilationUnit buildAst([Scope scope]) { + scope ??= _scope; return astFactory.compilationUnit( null, null, [ astFactory.partOfDirective( null, - null, + buildAnnotations(scope), $part, $of, null, astFactory.libraryIdentifier([ - astFactory.simpleIdentifier(stringToken(_name)), + astFactory.simpleIdentifier(stringToken("'$_uri'")), ]), $semicolon, ) @@ -119,15 +123,16 @@ class PartBuilder extends FileBuilder { } class _LibraryDirectiveBuilder implements AstBuilder { + final LibraryBuilder _library; final String _name; - _LibraryDirectiveBuilder(this._name); + _LibraryDirectiveBuilder(this._name, this._library); @override - LibraryDirective buildAst([_]) { + LibraryDirective buildAst([Scope scope]) { return astFactory.libraryDirective( null, - null, + _library.buildAnnotations(scope), $library, astFactory.libraryIdentifier([ astFactory.simpleIdentifier( @@ -139,8 +144,31 @@ class _LibraryDirectiveBuilder implements AstBuilder { } } +/// Lazily builds a [PartDirective] AST when built. +class PartBuilder extends Object + with HasAnnotationsMixin + implements AstBuilder { + final String _uri; + + factory PartBuilder(String uri) = PartBuilder._; + PartBuilder._(this._uri); + + @override + PartDirective buildAst([Scope scope]) { + return astFactory.partDirective( + null, + buildAnnotations(scope), + $part, + astFactory.simpleStringLiteral(stringToken("'$_uri'"), _uri), + $semicolon, + ); + } +} + /// Lazily builds an [ImportDirective] AST when built. -class ImportBuilder implements AstBuilder { +class ImportBuilder extends Object + with HasAnnotationsMixin + implements AstBuilder { final String _prefix; final String _uri; final bool _deferred; @@ -171,7 +199,7 @@ class ImportBuilder implements AstBuilder { } @override - ImportDirective buildAst([_]) { + ImportDirective buildAst([Scope scope]) { var combinators = []; if (_show.isNotEmpty) { combinators.add( @@ -191,7 +219,7 @@ class ImportBuilder implements AstBuilder { } return astFactory.importDirective( null, - null, + buildAnnotations(scope), null, astFactory.simpleStringLiteral(stringToken("'$_uri'"), _uri), null, @@ -205,7 +233,9 @@ class ImportBuilder implements AstBuilder { } /// Lazily builds an [ExportDirective] AST when built. -class ExportBuilder implements AstBuilder { +class ExportBuilder extends Object + with HasAnnotationsMixin + implements AstBuilder { final String _uri; final Set _show = new Set(); @@ -232,7 +262,7 @@ class ExportBuilder implements AstBuilder { } @override - ExportDirective buildAst([_]) { + ExportDirective buildAst([Scope scope]) { var combinators = []; if (_show.isNotEmpty) { combinators.add( @@ -252,7 +282,7 @@ class ExportBuilder implements AstBuilder { } return astFactory.exportDirective( null, - null, + buildAnnotations(scope), null, astFactory.simpleStringLiteral(stringToken("'$_uri'"), _uri), null, diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart index a25edbe..905a0e1 100644 --- a/lib/src/builders/method.dart +++ b/lib/src/builders/method.dart @@ -183,6 +183,7 @@ typedef void _AddParameter(ConstructorBuilder constructor); abstract class ConstructorBuilder implements AstBuilder, + HasAnnotations, HasParameters, HasStatements, ValidClassMember { @@ -193,8 +194,18 @@ abstract class ConstructorBuilder String name, String superName, List invokeSuper, + bool asConst, + bool asFactory, }) = _NormalConstructorBuilder; + /// Create a new [ConstructorBuilder] that redirects to another constructor. + factory ConstructorBuilder.redirectTo( + String name, + TypeBuilder redirectToClass, { + String constructor, + bool asConst, + }) = _RedirectingConstructorBuilder; + /// Adds a field initializer to this constructor. void addInitializer( String fieldName, { @@ -541,10 +552,78 @@ class _NamedParameterWrapper AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); } +class _RedirectingConstructorBuilder extends Object + with HasAnnotationsMixin, HasParametersMixin + implements ConstructorBuilder { + final String name; + final TypeBuilder redirectToClass; + final bool asConst; + final String constructor; + + _RedirectingConstructorBuilder( + this.name, + this.redirectToClass, { + this.asConst: false, + this.constructor, + }); + + @override + void addInitializer( + String fieldName, { + ExpressionBuilder toExpression, + String toParameter, + }) { + throw new UnsupportedError('Not valid for redirect constructors'); + } + + @override + void addStatement(StatementBuilder statement) { + throw new UnsupportedError('Not valid for redirect constructors'); + } + + @override + void addStatements(Iterable statements) { + throw new UnsupportedError('Not valid for redirect constructors'); + } + + @override + ConstructorDeclaration buildAst([_]) { + throw new UnsupportedError('Can only be built as part of a class.'); + } + + @override + ConstructorDeclaration buildConstructor( + TypeBuilder returnType, [ + Scope scope, + ]) { + return astFactory.constructorDeclaration( + null, + buildAnnotations(scope), + null, + asConst ? $const : null, + $factory, + returnType.buildType(scope).name, + name != null ? $period : null, + name != null ? stringIdentifier(name) : null, + buildParameterList(scope), + null, + null, + astFactory.constructorName( + redirectToClass.buildType(scope), + constructor != null ? $period : null, + constructor != null ? stringIdentifier(constructor) : null, + ), + astFactory.emptyFunctionBody($semicolon), + ); + } +} + class _NormalConstructorBuilder extends Object with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin implements ConstructorBuilder { final _initializers = {}; + final bool _asFactory; + final bool _asConst; final String _name; final String _superName; final List _superInvocation; @@ -553,10 +632,14 @@ class _NormalConstructorBuilder extends Object List invokeSuper, String name, String superName, + bool asConst: false, + bool asFactory: false, }) : _name = name, _superInvocation = invokeSuper, - _superName = superName; + _superName = superName, + _asConst = asConst, + _asFactory = asFactory; @override void addInitializer( @@ -611,8 +694,8 @@ class _NormalConstructorBuilder extends Object null, buildAnnotations(scope), null, - null, - null, + _asConst ? $const : null, + _asFactory ? $factory : null, returnType.buildType(scope).name, _name != null ? $period : null, _name != null ? stringIdentifier(_name) : null, diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart index 9ec4d56..0740094 100644 --- a/lib/src/builders/reference.dart +++ b/lib/src/builders/reference.dart @@ -32,15 +32,17 @@ class ReferenceBuilder extends Object with AbstractExpressionMixin, AbstractTypeBuilderMixin, TopLevelMixin implements AnnotationBuilder, ExpressionBuilder, TypeBuilder { final String _importFrom; - final String _name; - ReferenceBuilder._(this._name, [this._importFrom]); + /// Name of the reference. + final String name; + + ReferenceBuilder._(this.name, [this._importFrom]); @override Annotation buildAnnotation([Scope scope]) { return astFactory.annotation( $at, - identifier(scope, _name, _importFrom), + identifier(scope, name, _importFrom), null, null, null, @@ -54,14 +56,14 @@ class ReferenceBuilder extends Object Expression buildExpression([Scope scope]) { return identifier( scope, - _name, + name, _importFrom, ); } @override TypeName buildType([Scope scope]) { - return new TypeBuilder(_name, importFrom: _importFrom).buildType(scope); + return new TypeBuilder(name, importFrom: _importFrom).buildType(scope); } @override @@ -69,7 +71,7 @@ class ReferenceBuilder extends Object if (_importFrom == null) { throw new StateError('Cannot create an import - no URI provided'); } - return new ExportBuilder(_importFrom)..show(_name); + return new ExportBuilder(_importFrom)..show(name); } @override @@ -81,7 +83,7 @@ class ReferenceBuilder extends Object _importFrom, deferred: deferred, prefix: prefix, - )..show(_name); + )..show(name); } /// Returns a new [ReferenceBuilder] with [genericTypes]. @@ -91,7 +93,10 @@ class ReferenceBuilder extends Object /// reference('List').toTyped([reference('String')]) ReferenceBuilder toTyped(Iterable genericTypes) { return new _TypedReferenceBuilder( - genericTypes.toList(), _name, _importFrom); + genericTypes.toList(), + name, + _importFrom, + ); } } @@ -108,7 +113,7 @@ class _TypedReferenceBuilder extends ReferenceBuilder { @override TypeName buildType([Scope scope]) { return new TypeBuilder( - _name, + name, importFrom: _importFrom, genericTypes: _genericTypes, ) diff --git a/lib/src/builders/type/new_instance.dart b/lib/src/builders/type/new_instance.dart index 0a0441c..e1ac8f5 100644 --- a/lib/src/builders/type/new_instance.dart +++ b/lib/src/builders/type/new_instance.dart @@ -53,7 +53,9 @@ class _NewInvocationBuilderImpl extends Object $at, _type.buildType(scope).name, $period, - _constructor != null ? stringIdentifier(_constructor) : null, + _constructor != null && _constructor.isNotEmpty + ? stringIdentifier(_constructor) + : null, buildArgumentList(scope: scope), ); } @@ -64,8 +66,10 @@ class _NewInvocationBuilderImpl extends Object new KeywordToken(_keyword, 0), astFactory.constructorName( _type.buildType(scope), - _constructor != null ? $period : null, - _constructor != null ? stringIdentifier(_constructor) : null, + _constructor != null && _constructor.isNotEmpty ? $period : null, + _constructor != null && _constructor.isNotEmpty + ? stringIdentifier(_constructor) + : null, ), buildArgumentList(scope: scope), ); diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index 1ed0993..4b7b15e 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -71,6 +71,9 @@ final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); /// The `extends` token. final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); +/// The `factory` token. +final Token $factory = new KeywordToken(Keyword.FACTORY, 0); + /// The `false` token. final Token $false = new KeywordToken(Keyword.FALSE, 0); diff --git a/pubspec.yaml b/pubspec.yaml index 9ed6f6b..2737e18 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 1.0.0-beta+3 +version: 1.0.0-beta+4 description: A fluent API for generating Dart code author: Dart Team homepage: https://github.com/dart-lang/code_builder diff --git a/test/builders/file_test.dart b/test/builders/file_test.dart index be486fe..24dfe84 100644 --- a/test/builders/file_test.dart +++ b/test/builders/file_test.dart @@ -62,9 +62,41 @@ void main() { group('$LibraryBuilder', () { test('should handle empty methods', () { expect( - new LibraryBuilder() - ..addMember(new MethodBuilder.returnVoid('main')), - equalsSource('void main() {}')); + new LibraryBuilder()..addMember(new MethodBuilder.returnVoid('main')), + equalsSource('void main() {}'), + ); + }); + + test('should support metadata', () { + expect( + new LibraryBuilder('some_lib')..addAnnotation(reference('ignore')), + equalsSource(r''' + @ignore + library some_lib; + '''), + ); + }); + }); + + group('$PartBuilder', () { + test('should emit a part directive', () { + expect( + new LibraryBuilder()..addDirective(new PartBuilder('part.dart')), + equalsSource(r''' + part 'part.dart'; + '''), + ); + }); + }); + + group('$PartOfBuilder', () { + test('should emit a part-of file', () { + expect( + new PartOfBuilder('main.dart'), + equalsSource(r''' + part of 'main.dart'; + '''), + ); }); }); }); diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart index 8c89ee0..f757d48 100644 --- a/test/builders/method_test.dart +++ b/test/builders/method_test.dart @@ -121,6 +121,48 @@ void main() { ); }); + test('should emit a const constructor', () { + expect( + new ConstructorBuilder(asConst: true) + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + const Animal(); + '''), + ); + }); + + test('should emit a factory constructor', () { + expect( + (new ConstructorBuilder(asFactory: true) + ..addStatement(literal(null).asReturn())) + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + factory Animal() {return null;} + '''), + ); + }); + + test('should emit a redirecting constructor', () { + expect( + new ConstructorBuilder.redirectTo( + 'noopAnimal', + reference('NoopAnimalImpl'), + asConst: true, + ) + .buildConstructor(reference('Animal')) + .toSource(), + equalsIgnoringWhitespace(r''' + const factory Animal.noopAnimal() = NoopAnimalImpl; + '''), + ); + }); + test('should a method with a lambda value', () { expect( lambda('supported', literal(true), returnType: lib$core.bool),