diff --git a/.gitignore b/.gitignore index 8d48546..ae6cc48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -# See https://www.dartlang.org/tools/private-files.html - # Files and directories created by pub .buildlog .packages @@ -8,23 +6,9 @@ **/build **/packages -# Files created by dart2js -# (Most Dart developers will use pub build to compile Dart, use/modify these -# rules if you intend to use dart2js directly -# Convention is to use extension '.dart.js' for Dart compiled to Javascript to -# differentiate from explicit Javascript files) -*.dart.js -*.part.js -*.js.deps -*.js.map -*.info.json - # Directory created by dartdoc doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) pubspec.lock - -*.iml -.idea diff --git a/.travis.yml b/.travis.yml index 5826f9b..08997a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ dart: - dev - stable -script: ./tool/travis.sh +script: ./tool/presubmit.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb8ebe..ffc86f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.0-alpha + +- Large refactor that makes the library more feature complete. + ## 0.1.1 - Add concept of `Scope` and change `toAst` to support it diff --git a/README.md b/README.md index 334a4e0..df51b80 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # code_builder +[![pub package](https://img.shields.io/pub/v/code_builder.svg)](https://pub.dartlang.org/packages/code_builder) [![Build Status](https://travis-ci.org/dart-lang/code_builder.svg)](https://travis-ci.org/dart-lang/code_builder) -[![Coverage Status](https://coveralls.io/repos/dart-lang/code_builder/badge.svg)](https://coveralls.io/r/dart-lang/code_builder) +[![Coverage Status](https://coveralls.io/repos/github/dart-lang/code_builder/badge.svg?branch=master)](https://coveralls.io/github/dart-lang/code_builder?branch=master) `code_builder` is a fluent Dart API for generating valid Dart source code. @@ -34,10 +35,14 @@ Code builder has a narrow and user-friendly API. For example creating a class with a method: ```dart -new ClassBuilder('Animal', extends: 'Organism') - ..addMethod(new MethodBuilder.returnVoid('eat') - ..setExpression(new ExpressionBuilder.invoke('print', - positional: [new LiteralString('Yum!')]))); +var base = reference('Organism'); +var clazz = new ClassBuilder('Animal', asExtends: base); +clazz.addMethod( + new MethodBuilder.returnVoid( + 'eat', + returns: reference('print').call([literal('Yum')]), + ), +); ``` Outputs: @@ -53,26 +58,16 @@ use prefixes to avoid symbol conflicts: ```dart var lib = new LibraryBuilder.scope() - ..addDeclaration(new MethodBuilder( - name: 'doThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - )) - ..addDeclaration(new MethodBuilder( - name: 'doOtherThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/alternative.dart', - )) - ..addParameter(new ParameterBuilder( - 'thing', - type: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - ))); + ..addMembers([ + new MethodBuilder( + 'doThing', + returnType: reference('Thing', 'package:thing/thing.dart'), + ), + new MethodBuilder( + 'doOtherThing', + returnType: reference('Thing', 'package:thing/alternative.dart'), + ), + ]); ``` Outputs: @@ -81,5 +76,5 @@ import 'package:thing/thing.dart' as _i1; import 'package:thing/alternative.dart' as _i2; _i1.Thing doThing() {} -_i2.Thing doOtherThing(_i1.Thing thing) {} +_i2.Thing doOtherThing() {} ``` diff --git a/analysis_options.yaml b/analysis_options.yaml index f7145db..b256a69 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -15,7 +15,6 @@ linter: - valid_regexps - always_declare_return_types - annotate_overrides - - avoid_as - avoid_init_to_null - avoid_return_types_on_setters - await_only_futures @@ -25,17 +24,10 @@ linter: - empty_constructor_bodies - library_names - library_prefixes - - non_constant_identifier_names - only_throw_errors - overridden_fields - - package_api_docs - package_prefixed_library_names - prefer_is_not_empty - - public_member_api_docs - slash_for_doc_comments - - sort_constructors_first - - sort_unnamed_constructors_first - type_init_formals - - unnecessary_brace_in_string_interp - unnecessary_getters_setters - - package_names diff --git a/doc/SHORT_LINKS.md b/doc/SHORT_LINKS.md new file mode 100644 index 0000000..fc70631 --- /dev/null +++ b/doc/SHORT_LINKS.md @@ -0,0 +1,10 @@ +When using `goo.gl` links in the library, please re-use the following: + +- References to the Dart Library + - `dart:async`: https://goo.gl/Ulqbfz + - `dart:core`: https://goo.gl/XbSfmT + +- References to Github + - CONTRIBUTING.md: https://goo.gl/2LvV7f + - File an issue to update `dart/*.dart`: https://goo.gl/Xc6xAz + \ No newline at end of file diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 2df5a4d..9506468 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -2,69 +2,32 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -/// Code builder is a fluent Dart API for generating valid Dart source code. -/// -/// Generally speaking, code generation usually is done through a series of -/// string concatenation which results in messy and sometimes invalid code that -/// is not easily readable. -/// -/// Code builder uses the `analyzer` package to create real Dart language ASTs, -/// which, while not guaranteed to be correct, always follows the analyzer's -/// own understood format. -/// -/// Code builder also adds a more narrow and user-friendly API. For example -/// creating a class with a method is an easy affair: -/// new ClassBuilder('Animal', extends: 'Organism') -/// ..addMethod(new MethodBuilder.returnVoid('eat') -/// ..setExpression(new ExpressionBuilder.invoke('print', -/// positional: [new LiteralString('Yum!')]))); -/// -/// Outputs: -/// class Animal extends Organism { -/// void eat() => print('Yum!'); -/// } -/// -/// This package is in development and APIs are subject to frequent change. See -/// the `README.md` and `CONTRIBUTING.md` for more information. -library code_builder; - -import 'package:analyzer/analyzer.dart'; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:meta/meta.dart'; - -import 'src/analyzer_patch.dart'; -import 'src/tokens.dart'; - -part 'src/builders/annotation_builder.dart'; -part 'src/builders/class_builder.dart'; -part 'src/builders/constructor_builder.dart'; -part 'src/builders/expression_builder.dart'; -part 'src/builders/field_builder.dart'; -part 'src/builders/file_builder.dart'; -part 'src/builders/method_builder.dart'; -part 'src/builders/parameter_builder.dart'; -part 'src/builders/statement_builder.dart'; -part 'src/builders/type_builder.dart'; -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); - -SimpleIdentifier _stringIdentifier(String s) => - new SimpleIdentifier(stringToken(s)); - -Literal _stringLiteral(String s) => new SimpleStringLiteral(stringToken(s), s); - -/// Base class for building and emitting a Dart language [AstNode]. -abstract class CodeBuilder { - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// Uses [scope] to output an AST re-written to use appropriate prefixes. - A toAst([Scope scope = const Scope.identity()]); -} +export 'src/builders/annotation.dart' show AnnotationBuilder; +export 'src/builders/class.dart' + show asStatic, clazz, extend, implement, mixin, ClassBuilder; +export 'src/builders/expression.dart' + show literal, ExpressionBuilder, InvocationBuilder; +export 'src/builders/field.dart' + show varConst, varField, varFinal, FieldBuilder; +export 'src/builders/file.dart' show ImportBuilder, LibraryBuilder, PartBuilder; +export 'src/builders/method.dart' + show + constructor, + constructorNamed, + getter, + setter, + thisField, + lambda, + method, + named, + ConstructorBuilder, + MethodBuilder, + ValidMethodMember; +export 'src/builders/parameter.dart' show parameter, ParameterBuilder; +export 'src/pretty_printer.dart' show prettyToSource; +export 'src/builders/reference.dart' + show explicitThis, reference, ReferenceBuilder; +export 'src/builders/shared.dart' show AstBuilder, Scope; +export 'src/builders/statement.dart' + show ifThen, elseIf, elseThen, IfStatementBuilder, StatementBuilder; +export 'src/builders/type.dart' show NewInstanceBuilder, TypeBuilder; diff --git a/lib/dart/async.dart b/lib/dart/async.dart new file mode 100644 index 0000000..6025c39 --- /dev/null +++ b/lib/dart/async.dart @@ -0,0 +1,41 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Contains references to the `dart:async` SDK for use in code generation. +/// +/// This library is currently *experimental*, and is subject to change; it is +/// currently manually maintained but there might be a strong use case for this +/// to be automatically generated (at least partially) in the near future. +/// +/// ## Usage +/// +/// First import the library: +/// import 'package:code_builder/code_builder.dart'; +/// import 'package:code_builder/dart/async.dart'; +/// +/// All references are _namespaced_ under [lib$async]. Try it: +/// // Outputs: new Future.value('Hello') +/// async.Future.newInstanceNamed('value', [literal('Hello')]); +/// +/// If you are [missing a symbol from `dart:async`](https://goo.gl/XbSfmT) +/// please send us a [pull request](https://goo.gl/2LvV7f) or +/// [file an issue](https://goo.gl/IooPfl). +library code_builder.dart.async; + +import 'dart:async' as dart_async; + +import 'package:code_builder/code_builder.dart'; + +/// References to `dart:async`. +final DartAsync lib$async = new DartAsync._(); + +/// References to the `dart:async` library for code generation. See [lib$async]. +class DartAsync { + /// References [dart_async.Future]. + final ReferenceBuilder Future = _ref('Future'); + + DartAsync._(); + + static ReferenceBuilder _ref(String name) => reference(name, 'dart:async'); +} diff --git a/lib/dart/core.dart b/lib/dart/core.dart index f8c4e21..9a5a0d7 100644 --- a/lib/dart/core.dart +++ b/lib/dart/core.dart @@ -2,136 +2,225 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -/// Contains references to the `dart:core` library for use in code generation. +/// Contains references to the `dart:core` SDK for use in code generation. /// -/// This library is currently *experimental*, and is subject to change; it -/// currently is manually written but there might be a strong use case for this +/// This library is currently *experimental*, and is subject to change; it is +/// currently manually maintained but there might be a strong use case for this /// to be automatically generated (at least partially) in the near future. +/// +/// ## Usage +/// +/// First import the library: +/// import 'package:code_builder/code_builder.dart'; +/// import 'package:code_builder/dart/core.dart'; +/// +/// All references are _namespaced_ under [lib$core]. Try it: +/// // Outputs: print('Hello World') +/// core.print.call([literal('Hello World')]); +/// +/// If you are [missing a reference from `dart:core`](https://goo.gl/XbSfmT) +/// please send us a [pull request](https://goo.gl/2LvV7f) or +/// [file an issue](https://goo.gl/IooPfl). library code_builder.dart.core; +import 'dart:core' as dart_core; + import 'package:code_builder/code_builder.dart'; -/// An alias for `new AnnotationBuilder('override')`. -const AnnotationBuilder atOverride = const AnnotationBuilder.reference( - 'override', - 'dart:core', -); - -/// An alias for `new TypeBuilder('bool')`. -const TypeBuilder typeBool = const TypeBuilder( - 'bool', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('DateTime')`. -const TypeBuilder typeDateTime = const TypeBuilder( - 'DateTime', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('double')`. -const TypeBuilder typeDouble = const TypeBuilder( - 'double', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('Duration')`. -const TypeBuilder typeDuration = const TypeBuilder( - 'Duration', - importFrom: 'dart:core', -); - -/// An alias for `new TypeBuilder('Expando')`. -const TypeBuilder typeExpando = const TypeBuilder( - 'Expando', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Function')`. -const TypeBuilder typeFunction = const TypeBuilder( - 'Function', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('int')`. -const TypeBuilder typeInt = const TypeBuilder( - 'int', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Iterable')`. -const TypeBuilder typeIterable = const TypeBuilder( - 'Iterable', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('List')`. -const TypeBuilder typeList = const TypeBuilder( - 'List', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Map')`. -const TypeBuilder typeMap = const TypeBuilder( - 'Map', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Null')`. -const TypeBuilder typeNull = const TypeBuilder( - 'Null', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('num')`. -const TypeBuilder typeNum = const TypeBuilder( - 'num', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Object')`. -const TypeBuilder typeObject = const TypeBuilder( - 'Object', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Set')`. -const TypeBuilder typeSet = const TypeBuilder( - 'Set', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('String')`. -const TypeBuilder typeString = const TypeBuilder( - 'String', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Symbol')`. -const TypeBuilder typeSymbol = const TypeBuilder( - 'Symbol', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Type')`. -const TypeBuilder typeType = const TypeBuilder( - 'Type', - importFrom: 'dart:core', -); - -/// An alias for `const TypeBuilder('Uri')`. -const TypeBuilder typeUri = const TypeBuilder( - 'Uri', - importFrom: 'dart:core', -); - -/// Creates either a `@deprecated` or `@Deprecated('Message')` annotation. -AnnotationBuilder atDeprecated([String message]) { - if (message == null) { - return new AnnotationBuilder.reference('deprecated', 'dart:core'); +/// A namespace for references in `dart:core`. +final DartCore lib$core = new DartCore._(); + +/// References to the `dart:core` library for code generation. See [lib$core]. +class DartCore { + /// References [dart_core.bool]. + final ReferenceBuilder bool = _ref('bool'); + + /// References [dart_core.identical]. + final ReferenceBuilder identical = _ref('identical'); + + /// References [dart_core.deprecated]. + final ReferenceBuilder deprecated = _ref('deprecated'); + + /// References [dart_core.override]. + final ReferenceBuilder override = _ref('override'); + + /// References [dart_core.print]. + final ReferenceBuilder print = _ref('print'); + + /// References [dart_core.identityHashCode]. + final ReferenceBuilder identityHashCode = _ref('identityHashCode'); + + /// References [dart_core.int]. + final ReferenceBuilder int = _ref('int'); + + /// References [dart_core.AbstractClassInstantiationError]. + final ReferenceBuilder AbstractClassInstantiationError = + _ref('AbstractClassInstantiationError'); + + /// References [dart_core.ArgumentError]. + final ReferenceBuilder ArgumentError = _ref('ArgumentError'); + + /// References [dart_core.AssertionError]. + final ReferenceBuilder AssertionError = _ref('AssertionError'); + + /// References [dart_core.BidirectionalIterator]. + final ReferenceBuilder BidirectionalIterator = _ref('BidirectionalIterator'); + + /// References [dart_core.CastError]. + final ReferenceBuilder CastError = _ref('CastError'); + + /// References [dart_core.Comparable]. + final ReferenceBuilder Comparable = _ref('Comparable'); + + /// References [dart_core.Comparator] + final ReferenceBuilder Comparator = _ref('Comparator'); + + /// References [dart_core.ConcurrentModificationError]. + final ReferenceBuilder ConcurrentModificationError = + _ref('ConcurrentModificationError'); + + /// References [dart_core.CyclicInitializationError]. + final ReferenceBuilder CyclicInitializationError = + _ref('CyclicInitializationError'); + + /// References [dart_core.DateTime]. + final ReferenceBuilder DateTime = _ref('DateTime'); + + /// References [dart_core.Deprecated]. + final ReferenceBuilder Deprecated = _ref('Deprecated'); + + /// References [dart_core.Duration]. + final ReferenceBuilder Duration = _ref('Duration'); + + /// References [dart_core.Error]. + final ReferenceBuilder Error = _ref('Error'); + + /// References [dart_core.Exception]. + final ReferenceBuilder Exception = _ref('Exception'); + + /// References [dart_core.Expando]. + final ReferenceBuilder Expando = _ref('Expando'); + + /// References [dart_core.FallThroughError]. + final ReferenceBuilder FallThroughError = _ref('FallThroughError'); + + /// References [dart_core.FormatException]. + final ReferenceBuilder FormatException = _ref('FormatException'); + + /// References [dart_core.Function]. + final ReferenceBuilder Function = _ref('Function'); + + /// References [dart_core.IndexError]. + final ReferenceBuilder IndexError = _ref('IndexError'); + + /// References [dart_core.IntegerDivisionByZeroException]. + final ReferenceBuilder IntegerDivisionByZeroException = + _ref('IntegerDivisionByZeroException'); + + /// References [dart_core.Invocation]. + final ReferenceBuilder Invocation = _ref('Invocation'); + + /// References [dart_core.Iterable]. + final ReferenceBuilder Iterable = _ref('Iterable'); + + /// References [dart_core.Iterator]. + final ReferenceBuilder Iterator = _ref('Iterator'); + + /// References [dart_core.List]. + final ReferenceBuilder List = _ref('List'); + + /// References [dart_core.Map]. + final ReferenceBuilder Map = _ref('Map'); + + /// References [dart_core.Match]. + final ReferenceBuilder Match = _ref('Match'); + + /// References [dart_core.NoSuchMethodError]. + final ReferenceBuilder NoSuchMethodError = _ref('NoSuchMethodError'); + + /// References [dart_core.Null]. + final ReferenceBuilder Null = _ref('Null'); + + /// References [dart_core.NullThrownError]. + final ReferenceBuilder NullThrownError = _ref('NullThrownError'); + + /// References [dart_core.Object]. + final ReferenceBuilder Object = _ref('Object'); + + /// References [dart_core.OutOfMemoryError]. + final ReferenceBuilder OutOfMemoryError = _ref('OutOfMemoryError'); + + /// References [dart_core.Pattern]. + final ReferenceBuilder Pattern = _ref('Pattern'); + + /// References [dart_core.RangeError]. + final ReferenceBuilder RangeError = _ref('RangeError'); + + /// References [dart_core.RegExp]. + final ReferenceBuilder RegExp = _ref('RegExp'); + + /// References [dart_core.RuneIterator]. + final ReferenceBuilder RuneIterator = _ref('RuneIterator'); + + /// References [dart_core.Runes]. + final ReferenceBuilder Runes = _ref('Runes'); + + /// References [dart_core.Set]. + final ReferenceBuilder Set = _ref('Set'); + + /// References [dart_core.Sink]. + final ReferenceBuilder Sink = _ref('Sink'); + + /// References [dart_core.StackOverflowError]. + final ReferenceBuilder StackOverflowError = _ref('StackOverflowError'); + + /// References [dart_core.StackTrace]. + final ReferenceBuilder StackTrace = _ref('StackTrace'); + + /// References [dart_core.StateError]. + final ReferenceBuilder StateError = _ref('StateError'); + + /// References [dart_core.Stopwatch]. + final ReferenceBuilder Stopwatch = _ref('Stopwatch'); + + /// References [dart_core.String]. + final ReferenceBuilder String = _ref('String'); + + /// References [dart_core.StringBuffer]. + final ReferenceBuilder StringBuffer = _ref('StringBuffer'); + + /// References [dart_core.StringSink]. + final ReferenceBuilder StringSink = _ref('StringSink'); + + /// References [dart_core.Symbol]. + final ReferenceBuilder Symbol = _ref('Symbol'); + + /// References [dart_core.Type]. + final ReferenceBuilder Type = _ref('Type'); + + /// References [dart_core.TypeError]. + final ReferenceBuilder TypeError = _ref('TypeError'); + + /// References [dart_core.UnimplementedError]. + final ReferenceBuilder UnimplementedError = _ref('UnimplementedError'); + + /// References [dart_core.UnsupportedError]. + final ReferenceBuilder UnsupportedError = _ref('UnsupportedError'); + + /// References [dart_core.Uri]. + final ReferenceBuilder Uri = _ref('Uri'); + + /// References [dart_core.UriData]. + final ReferenceBuilder UriData = _ref('UriData'); + + /// References `void` type for returning nothing in a method. + /// + /// **NOTE**: As a language limitation, this cannot be named `void`. + final TypeBuilder $void = new TypeBuilder('void'); + + DartCore._(); + + static ReferenceBuilder _ref(dart_core.String name) { + return reference(name, 'dart:core'); } - return new AnnotationBuilder.invoke( - 'Deprecated', - positional: [new LiteralString(message)], - ); } diff --git a/lib/src/analyzer_patch.dart b/lib/src/analyzer_patch.dart index 5b27d77..b4a7149 100644 --- a/lib/src/analyzer_patch.dart +++ b/lib/src/analyzer_patch.dart @@ -10,9 +10,6 @@ import 'package:analyzer/src/generated/java_core.dart'; class PrintBuffer implements PrintWriter, StringBuffer { final StringBuffer _impl = new StringBuffer(); - @override - void clear() {} - @override bool get isEmpty => _impl.isEmpty; @@ -22,6 +19,9 @@ class PrintBuffer implements PrintWriter, StringBuffer { @override int get length => _impl.length; + @override + void clear() {} + @override void newLine() { _impl.writeln(); @@ -40,6 +40,9 @@ class PrintBuffer implements PrintWriter, StringBuffer { _impl.writeln(s); } + @override + String toString() => _impl.toString(); + @override void write(Object obj) { _impl.write(obj); @@ -59,7 +62,4 @@ class PrintBuffer implements PrintWriter, StringBuffer { void writeln([Object obj = ""]) { _impl.writeln(obj); } - - @override - String toString() => _impl.toString(); } diff --git a/lib/src/builders/annotation.dart b/lib/src/builders/annotation.dart new file mode 100644 index 0000000..404088a --- /dev/null +++ b/lib/src/builders/annotation.dart @@ -0,0 +1,49 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/parameter.dart'; +import 'package:code_builder/src/builders/shared.dart'; + +/// Builds an [Annotation] AST when [buildAnnotation] is invoked. +abstract class AnnotationBuilder + implements ValidClassMember, ValidParameterMember { + /// Returns an [Annotation] AST representing the builder. + Annotation buildAnnotation([Scope scope]); +} + +/// An [AstBuilder] that can be annotated with [AnnotationBuilder]. +abstract class HasAnnotations implements AstBuilder { + /// Adds [annotation] to the builder. + void addAnnotation(AnnotationBuilder annotation); + + /// Adds [annotations] to the builder. + void addAnnotations(Iterable annotations); +} + +/// Implements [HasAnnotations]. +abstract class HasAnnotationsMixin implements HasAnnotations { + final List _annotations = []; + + @override + void addAnnotation(AnnotationBuilder annotation) { + _annotations.add(annotation); + } + + @override + void addAnnotations(Iterable annotations) { + _annotations.addAll(annotations); + } + + /// Returns a [List] of all built [Annotation]s. + List buildAnnotations([Scope scope]) => _annotations + .map/**/((a) => a.buildAnnotation(scope)) + .toList(); + + /// Clones all annotations to [clone]. + void copyAnnotationsTo(HasAnnotations clone) { + clone.addAnnotations(_annotations); + } +} diff --git a/lib/src/builders/annotation_builder.dart b/lib/src/builders/annotation_builder.dart deleted file mode 100644 index 1ce7b70..0000000 --- a/lib/src/builders/annotation_builder.dart +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Builds a metadata [Annotation] to be added to other AST elements. -/// -/// An annotation may either be a reference to an existing `const` identifier -/// _or_ to an invocation of a `const` class constructor. -/// -/// To create a `@doNotUse` reference, use [AnnotationBuilder.reference]: -/// const doNotUse = #do_not_use; -/// -/// @doNotUse -/// void destroyTheWorld() { ... } -/// -/// To create a `@DoNotUse('Blows up')` use [AnnotationBuilder.invoke]. -abstract class AnnotationBuilder implements CodeBuilder { - /// Create a new annotated `const` [constructor] invocation. - /// - /// May optionally specify an [importFrom] to auto-prefix the annotation _if_ - /// attached to a library where the import is not specified or is prefixed. - factory AnnotationBuilder.invoke( - String constructor, { - String importFrom, - Iterable> positional: const [], - Map> named: const {}, - }) { - return new _ConstructorAnnotationBuilder( - constructor, - new ExpressionBuilder.invoke( - null, - positional: positional, - named: named, - ), - importFrom, - ); - } - - /// Create a new annotated `const` [identifier]. - /// - /// May optionally specify an [importFrom] to auto-prefix the annotation _if_ - /// attached to a library where the import is not specified or is prefixed. - const factory AnnotationBuilder.reference(String identifier, - [String importFrom]) = _ReferenceAnnotationBuilder; -} - -class _ConstructorAnnotationBuilder implements AnnotationBuilder { - final String _constructor; - final ExpressionBuilder _expression; - final String _importFrom; - - _ConstructorAnnotationBuilder(this._constructor, this._expression, - [this._importFrom]); - - @override - Annotation toAst([Scope scope = const Scope.identity()]) { - var expressionAst = _expression.toAst(scope); - if (expressionAst is MethodInvocation) { - return new Annotation( - null, - scope.getIdentifier(_constructor, _importFrom), - null, - null, - // TODO(matanl): InvocationExpression needs to be public API. - expressionAst.argumentList, - ); - } - throw new UnsupportedError('Expression must be InvocationExpression'); - } -} - -class _ReferenceAnnotationBuilder implements AnnotationBuilder { - final String _importFrom; - final String _reference; - - const _ReferenceAnnotationBuilder(this._reference, [this._importFrom]); - - @override - Annotation toAst([Scope scope = const Scope.identity()]) => new Annotation( - null, - scope.getIdentifier(_reference, _importFrom), - null, - null, - null, - ); -} diff --git a/lib/src/builders/class.dart b/lib/src/builders/class.dart new file mode 100644 index 0000000..333d3e4 --- /dev/null +++ b/lib/src/builders/class.dart @@ -0,0 +1,273 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/field.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// A more short-hand way of constructing a [ClassBuilder]. +ClassBuilder clazz( + String name, [ + Iterable members = const [], +]) { + final clazz = new ClassBuilder(name); + for (final member in members) { + if (member is AnnotationBuilder) { + clazz.addAnnotation(member); + } else if (member is _TypeNameWrapper) { + if (member.extend) { + clazz.setExtends(member.type); + } else if (member.mixin) { + clazz.addMixin(member.type); + } else { + clazz.addImplement(member.type); + } + } else if (member is ConstructorBuilder) { + clazz.addConstructor(member); + } else if (member is _StaticFieldWrapper) { + var wrapped = member._member; + if (wrapped is MethodBuilder) { + clazz.addMethod(wrapped, asStatic: true); + } else { + clazz.addField(wrapped as FieldBuilder, asStatic: true); + } + } else if (member is FieldBuilder) { + clazz.addField(member); + } else if (member is MethodBuilder) { + clazz.addMethod(member); + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + return clazz; +} + +/// Wrap [member] to be emitted as a `static` method or field. +ValidClassMember asStatic(ValidClassMember member) { + return new _StaticFieldWrapper(member); +} + +class _StaticFieldWrapper implements ValidClassMember { + final ValidClassMember _member; + + _StaticFieldWrapper(this._member); + + @override + AstNode buildAst([Scope scope]) => + throw new UnsupportedError('Use inside varField'); +} + +/// Returns a wrapper around [type] for use with [clazz]. +_TypeNameWrapper extend(TypeBuilder type) { + return new _TypeNameWrapper( + type, + extend: true, + ); +} + +/// Returns a wrapper around [type] for use with [clazz]. +_TypeNameWrapper implement(TypeBuilder type) { + return new _TypeNameWrapper( + type, + implement: true, + ); +} + +/// Returns a wrapper around [type] for use with [clazz]. +_TypeNameWrapper mixin(TypeBuilder type) { + return new _TypeNameWrapper( + type, + mixin: true, + ); +} + +/// Lazily builds an [ClassDeclaration] AST when [buildClass] is invoked. +abstract class ClassBuilder + implements AstBuilder, HasAnnotations, TypeBuilder { + /// Returns a new [ClassBuilder] with [name]. + factory ClassBuilder( + String name, { + bool asAbstract, + TypeBuilder asExtends, + Iterable asWith, + Iterable asImplements, + }) = _ClassBuilderImpl; + + /// Adds a [constructor]. + void addConstructor(ConstructorBuilder constructor); + + /// Adds a [field]. + void addField(FieldBuilder field, {bool asStatic: false}); + + /// Adds an [interface] to implement. + void addImplement(TypeBuilder interface); + + /// Adds [interfaces] to implement. + void addImplements(Iterable interfaces); + + /// Adds a [method]. + void addMethod(MethodBuilder method, {bool asStatic: false}); + + /// Adds a [mixin]. + void addMixin(TypeBuilder mixin); + + /// Adds [mixins]. + void addMixins(Iterable mixins); + + /// Returns an [ClassDeclaration] AST representing the builder. + ClassDeclaration buildClass([Scope scope]); + + /// Sets [extend]. + void setExtends(TypeBuilder extend); +} + +/// A marker interface for an AST that could be added to [ClassBuilder]. +abstract class ValidClassMember implements AstBuilder {} + +class _ClassBuilderImpl extends Object + with AbstractTypeBuilderMixin, HasAnnotationsMixin + implements ClassBuilder { + final _constructors = []; + final _fields = {}; + final _methods = {}; + + TypeBuilder _extends; + final List _with; + final List _implements; + + final bool _asAbstract; + final String _name; + + _ClassBuilderImpl( + this._name, { + bool asAbstract: false, + TypeBuilder asExtends, + Iterable asWith: const [], + Iterable asImplements: const [], + }) + : _asAbstract = asAbstract, + _extends = asExtends, + _with = asWith.toList(), + _implements = asImplements.toList(); + + @override + void addConstructor(ConstructorBuilder constructor) { + _constructors.add(constructor); + } + + @override + void addField(FieldBuilder field, {bool asStatic: false}) { + _fields[field] = asStatic; + } + + @override + void addImplement(TypeBuilder interface) { + _implements.add(interface); + } + + @override + void addImplements(Iterable interfaces) { + _implements.addAll(interfaces); + } + + @override + void addMethod(MethodBuilder method, {bool asStatic: false}) { + _methods[method] = asStatic; + } + + @override + void addMixin(TypeBuilder mixin) { + _with.add(mixin); + } + + @override + void addMixins(Iterable mixins) { + _with.addAll(mixins); + } + + @override + ClassDeclaration buildAst([Scope scope]) => buildClass(scope); + + @override + ClassDeclaration buildClass([Scope scope]) { + var extend = _extends; + if (extend == null && _with.isNotEmpty) { + extend = lib$core.Object; + } + final clazz = new ClassDeclaration( + null, + buildAnnotations(scope), + _asAbstract ? $abstract : null, + $class, + stringIdentifier(_name), + null, + extend != null + ? new ExtendsClause( + $extends, + extend.buildType(scope), + ) + : null, + _with.isNotEmpty + ? new WithClause( + $with, + _with.map/**/((w) => w.buildType(scope)).toList(), + ) + : null, + _implements.isNotEmpty + ? new ImplementsClause( + $implements, + _implements.map/**/((i) => i.buildType(scope)).toList(), + ) + : null, + null, + null, + null, + ); + _fields.forEach((field, static) { + clazz.members.add(field.buildField(static, scope)); + }); + _constructors.forEach((constructor) { + clazz.members.add(constructor.buildConstructor( + this, + scope, + )); + }); + _methods.forEach((method, static) { + clazz.members.add(method.buildMethod(static, scope)); + }); + return clazz; + } + + @override + TypeName buildType([Scope scope]) { + return new TypeBuilder(_name).buildType(scope); + } + + @override + void setExtends(TypeBuilder extend) { + _extends = extend; + } +} + +class _TypeNameWrapper implements ValidClassMember { + final bool extend; + final bool implement; + final bool mixin; + final TypeBuilder type; + + _TypeNameWrapper( + this.type, { + this.extend: false, + this.implement: false, + this.mixin: false, + }); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within clazz'); +} diff --git a/lib/src/builders/class_builder.dart b/lib/src/builders/class_builder.dart deleted file mode 100644 index bd6a260..0000000 --- a/lib/src/builders/class_builder.dart +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Builds a [ClassDeclaration] AST. -class ClassBuilder implements CodeBuilder { - final String _name; - final bool _isAbstract; - final TypeBuilder _extend; - final Iterable _implement; - final Iterable _mixin; - - final List _fields = []; - final List _methods = []; - final List _metadata = []; - final List _constructors = []; - - /// Create a new builder for a `class` named [name]. - /// - /// Optionally, define another class to [extend] or classes to either - /// [implement] or [mixin]. - factory ClassBuilder( - String name, { - TypeBuilder extend, - Iterable implement: const [], - Iterable mixin: const [], - }) => - new ClassBuilder._( - name, - false, - extend, - new List.unmodifiable(implement), - new List.unmodifiable(mixin), - ); - - /// Create a new builder for an `abstract class` named [name]. - factory ClassBuilder.asAbstract(String name, - {TypeBuilder extend, - Iterable implement: const [], - Iterable mixin: const []}) => - new ClassBuilder._( - name, - true, - extend, - new List.unmodifiable(implement), - new List.unmodifiable(mixin), - ); - - ClassBuilder._( - this._name, - this._isAbstract, - this._extend, - this._implement, - this._mixin, - ); - - /// Adds an annotation [builder] as metadata. - void addAnnotation(AnnotationBuilder builder) { - _metadata.add(builder); - } - - /// Adds a constructor [builder]. - void addConstructor(ConstructorBuilder builder) { - _constructors.add(builder); - } - - /// Adds a field [builder] as a member on the class. - void addField(FieldBuilder builder) { - _fields.add(builder); - } - - /// Adds a method [builder] as a member on the class. - void addMethod(MethodBuilder builder) { - _methods.add(builder); - } - - @override - ClassDeclaration toAst([Scope scope = const Scope.identity()]) { - var astNode = _emptyClassDeclaration()..name = _stringIdentifier(_name); - if (_isAbstract) { - astNode.abstractKeyword = $abstract; - } - if (_extend != null) { - astNode.extendsClause = new ExtendsClause($extends, _extend.toAst(scope)); - } - if (_implement.isNotEmpty) { - astNode.implementsClause = new ImplementsClause($implements, - _implement.map/**/((i) => i.toAst(scope)).toList()); - } - if (_mixin.isNotEmpty) { - astNode.withClause = new WithClause( - $with, _mixin.map/**/((i) => i.toAst(scope)).toList()); - } - astNode - ..metadata.addAll(_metadata.map/**/((a) => a.toAst(scope))); - astNode - ..members.addAll(_fields.map/**/((f) => f.toFieldAst(scope))) - ..members.addAll(_constructors.map/**/( - (c) => c.toAst(scope)..returnType = _stringIdentifier(_name))) - ..members - .addAll(_methods.map/**/((m) => m.toMethodAst(scope))); - return astNode; - } - - static ClassDeclaration _emptyClassDeclaration() => new ClassDeclaration( - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ); -} diff --git a/lib/src/builders/constructor_builder.dart b/lib/src/builders/constructor_builder.dart deleted file mode 100644 index 8758d8b..0000000 --- a/lib/src/builders/constructor_builder.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Builds a [ConstructorDeclaration] AST. -/// -/// Similar to [MethodBuilder] but with constructor-only features. -class ConstructorBuilder implements CodeBuilder { - final bool _isConstant; - final String _name; - final List _parameters = []; - - /// Create a new builder for a constructor, optionally with a [name]. - factory ConstructorBuilder([String name]) { - return new ConstructorBuilder._(false, name); - } - - /// Create a new builder for a constructor, optionally with a [name]. - /// - /// The resulting constructor will be `const`. - factory ConstructorBuilder.isConst([String name]) { - return new ConstructorBuilder._(true, name); - } - - ConstructorBuilder._(this._isConstant, this._name); - - /// Lazily adds [builder]. - /// - /// 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, - _isConstant ? $const : null, - null, - null, - _name != null ? _stringIdentifier(_name) : null, - MethodBuilder._emptyParameters() - ..parameters.addAll( - _parameters.map/**/((p) => p.toAst(scope))), - null, - null, - null, - new EmptyFunctionBody($semicolon), - ); - return astNode; - } -} diff --git a/lib/src/builders/expression.dart b/lib/src/builders/expression.dart new file mode 100644 index 0000000..faa78c7 --- /dev/null +++ b/lib/src/builders/expression.dart @@ -0,0 +1,359 @@ +library code_builder.src.builders.expression; + +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/src/builders/parameter.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/statement/if.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +part 'expression/assert.dart'; +part 'expression/assign.dart'; +part 'expression/invocation.dart'; +part 'expression/negate.dart'; +part 'expression/operators.dart'; +part 'expression/return.dart'; + +final _false = new BooleanLiteral(new KeywordToken(Keyword.FALSE, 0), true); + +final _null = new NullLiteral(new KeywordToken(Keyword.NULL, 0)); + +final _true = new BooleanLiteral(new KeywordToken(Keyword.TRUE, 0), true); + +/// Returns a pre-defined literal expression of [value]. +/// +/// Only primitive values are allowed. +ExpressionBuilder literal(value) => new _LiteralExpression(_literal(value)); + +Literal _literal(value) { + if (value == null) { + return _null; + } else if (value is bool) { + return value ? _true : _false; + } else if (value is String) { + return new SimpleStringLiteral(stringToken("'$value'"), value); + } else if (value is int) { + return new IntegerLiteral(stringToken('$value'), value); + } else if (value is double) { + return new DoubleLiteral(stringToken('$value'), value); + } else if (value is List) { + return new ListLiteral( + null, + null, + $openBracket, + value.map/**/(_literal).toList(), + $closeBracket, + ); + } else if (value is Map) { + return new MapLiteral( + null, + null, + $openBracket, + value.keys.map/**/((k) { + return new MapLiteralEntry(_literal(k), $colon, _literal(value[k])); + }).toList(), + $closeBracket, + ); + } + throw new ArgumentError.value(value, 'Unsupported'); +} + +/// Implements much of [ExpressionBuilder]. +abstract class AbstractExpressionMixin implements ExpressionBuilder { + @override + ExpressionBuilder operator *(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $multiply, + ); + } + + @override + ExpressionBuilder operator +(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $plus, + ); + } + + @override + ExpressionBuilder operator -(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $minus, + ); + } + + @override + ExpressionBuilder operator /(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $divide, + ); + } + + @override + StatementBuilder asAssert() => new _AsAssert(this); + + @override + StatementBuilder asAssign( + String variable, { + bool nullAware: false, + }) => + new _AsAssign(this, variable, nullAware); + + @override + StatementBuilder asConst(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $const); + } + + @override + StatementBuilder asFinal(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $final); + } + + @override + IfStatementBuilder asIf() => new IfStatementBuilder(this); + + @override + StatementBuilder asReturn() => new _AsReturn(this); + + @override + StatementBuilder asStatement() => new _AsStatement(this); + + @override + StatementBuilder asVar(String variable, [TypeBuilder type]) { + return new _AsAssignNew(this, variable, type, $var); + } + + @override + Statement buildStatement([Scope scope]) { + return asStatement().buildStatement(scope); + } + + @override + InvocationBuilder call( + Iterable positionalArguments, [ + Map namedArguments = const {}, + ]) { + final invocation = new InvocationBuilder._(this); + positionalArguments.forEach(invocation.addPositionalArgument); + namedArguments.forEach(invocation.addNamedArgument); + return invocation; + } + + @override + ExpressionBuilder equals(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $equalsEquals, + ); + } + + @override + ExpressionBuilder identical(ExpressionBuilder other) { + return lib$core.identical.call([ + this, + other, + ]); + } + + @override + InvocationBuilder invoke( + String method, + Iterable positionalArguments, [ + Map namedArguments = const {}, + ]) { + final invocation = new InvocationBuilder._on(this, method); + positionalArguments.forEach(invocation.addPositionalArgument); + namedArguments.forEach(invocation.addNamedArgument); + return invocation; + } + + @override + ExpressionBuilder negate() => new _NegateExpression(this); + + @override + ExpressionBuilder negative() => new _NegativeExpression(this); + + @override + ExpressionBuilder notEquals(ExpressionBuilder other) { + return new _AsBinaryExpression( + this, + other, + $notEquals, + ); + } + + @override + ExpressionBuilder parentheses() => new _ParenthesesExpression(this); +} + +/// Builds an [Expression] AST when [buildExpression] is invoked. +abstract class ExpressionBuilder + implements AstBuilder, StatementBuilder, ValidParameterMember { + /// Returns as an [ExpressionBuilder] multiplying by [other]. + ExpressionBuilder operator *(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] adding [other]. + ExpressionBuilder operator +(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] subtracting [other]. + ExpressionBuilder operator -(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] dividing by [other]. + ExpressionBuilder operator /(ExpressionBuilder other); + + /// Return as a [StatementBuilder] that `assert`s this expression. + StatementBuilder asAssert(); + + /// Returns as a [StatementBuilder] that assigns to an existing [variable]. + StatementBuilder asAssign(String variable, {bool nullAware}); + + /// Returns as a [StatementBuilder] that assigns to a new `const` [variable]. + StatementBuilder asConst(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that assigns to a new `final` [variable]. + StatementBuilder asFinal(String variable, [TypeBuilder type]); + + /// Returns as a [StatementBuilder] that builds an `if` statement. + IfStatementBuilder asIf(); + + /// Returns as a [StatementBuilder] that `return`s this expression. + StatementBuilder asReturn(); + + /// Returns _explicitly_ as a [StatementBuilder]. + /// + /// **NOTE**: [ExpressionBuilder] is _already_ usable as a [StatementBuilder] + /// directly; this API exists in order force [buildAst] to return a + /// [Statement] AST instead of an expression. + StatementBuilder asStatement(); + + /// Returns as a [StatementBuilder] that assigns to a new `var` [variable]. + /// + /// If [type] is supplied, the resulting statement is `{type} {variable} =`. + StatementBuilder asVar(String variable, [TypeBuilder type]); + + /// Returns an [Expression] AST representing the builder. + Expression buildExpression([Scope scope]); + + /// Returns as an [InvocationBuilder] with arguments added. + InvocationBuilder call( + Iterable positionalArguments, [ + Map namedArguments, + ]); + + /// Returns as an [ExpressionBuilder] comparing using `==` against [other]. + ExpressionBuilder equals(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] comparing using `identical`. + ExpressionBuilder identical(ExpressionBuilder other); + + /// Returns as an [InvocationBuilder] on [method] of this expression. + InvocationBuilder invoke( + String method, + Iterable positionalArguments, [ + Map namedArguments, + ]); + + /// Returns as an [ExpressionBuilder] negating using the `!` operator. + ExpressionBuilder negate(); + + /// Returns as an [ExpressionBuilder] negating the value, + ExpressionBuilder negative(); + + /// Returns as an [ExpressionBuilder] comparing using `!=` against [other]. + ExpressionBuilder notEquals(ExpressionBuilder other); + + /// Returns as an [ExpressionBuilder] wrapped in parentheses. + ExpressionBuilder parentheses(); +} + +/// An [AstBuilder] that can add [ExpressionBuilder]. +abstract class HasExpressions implements AstBuilder { + final List _expressions = []; + + /// Adds [expression] to the builder. + void addExpression(ExpressionBuilder expression) { + _expressions.add(expression); + } + + /// Adds [expressions] to the builder. + void addExpressions(Iterable expressions) { + _expressions.addAll(expressions); + } +} + +/// Implements [HasExpressions]. +abstract class HasExpressionsMixin extends HasExpressions { + /// Returns a [List] of all built [Expression]s. + List buildExpressions([Scope scope]) => _expressions + .map/**/((e) => e.buildExpression(scope)) + .toList(); + + /// Clones all expressions to [clone]. + void cloneExpressionsTo(HasExpressions clone) { + clone.addExpressions(_expressions); + } +} + +class _AsStatement implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsStatement(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new ExpressionStatement( + _expression.buildExpression(scope), + $semicolon, + ); + } +} + +class _LiteralExpression extends Object with AbstractExpressionMixin { + final Literal _literal; + + _LiteralExpression(this._literal); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([_]) => _literal; +} + +class _ParenthesesExpression extends Object with AbstractExpressionMixin { + final ExpressionBuilder _expression; + + _ParenthesesExpression(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new ParenthesizedExpression( + $openParen, + _expression.buildExpression(scope), + $closeParen, + ); + } +} diff --git a/lib/src/builders/expression/assert.dart b/lib/src/builders/expression/assert.dart new file mode 100644 index 0000000..3313fee --- /dev/null +++ b/lib/src/builders/expression/assert.dart @@ -0,0 +1,23 @@ +part of code_builder.src.builders.expression; + +class _AsAssert implements StatementBuilder { + final ExpressionBuilder _expression; + + _AsAssert(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new AssertStatement( + $assert, + $openParen, + _expression.buildExpression(scope), + null, + null, + $closeParen, + null, + ); + } +} diff --git a/lib/src/builders/expression/assign.dart b/lib/src/builders/expression/assign.dart new file mode 100644 index 0000000..5c6b191 --- /dev/null +++ b/lib/src/builders/expression/assign.dart @@ -0,0 +1,53 @@ +part of code_builder.src.builders.expression; + +class _AsAssign extends AbstractExpressionMixin { + final String _name; + final bool _nullAware; + final ExpressionBuilder _value; + + _AsAssign(this._value, this._name, this._nullAware); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new AssignmentExpression( + stringIdentifier(_name), + _nullAware ? $nullAwareEquals : $equals, + _value.buildExpression(scope), + ); + } +} + +class _AsAssignNew implements StatementBuilder { + final ExpressionBuilder _value; + final String _name; + final TypeBuilder _type; + final Token _modifier; + + _AsAssignNew(this._value, this._name, this._type, this._modifier); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new VariableDeclarationStatement( + new VariableDeclarationList( + null, + null, + _type == null || _modifier != $var ? _modifier : null, + _type?.buildType(scope), + [ + new VariableDeclaration( + stringIdentifier(_name), + $equals, + _value.buildExpression(scope), + ), + ], + ), + $semicolon, + ); + } +} diff --git a/lib/src/builders/expression/invocation.dart b/lib/src/builders/expression/invocation.dart new file mode 100644 index 0000000..1c015e6 --- /dev/null +++ b/lib/src/builders/expression/invocation.dart @@ -0,0 +1,96 @@ +part of code_builder.src.builders.expression; + +/// Partial implementation of [InvocationBuilder]. +abstract class AbstractInvocationBuilderMixin implements InvocationBuilder { + final List _positional = []; + final Map _named = {}; + + @override + void addNamedArgument(String name, ExpressionBuilder argument) { + _named[name] = argument; + } + + @override + void addPositionalArgument(ExpressionBuilder argument) { + _positional.add(argument); + } + + /// Returns an [ArgumentList] AST. + ArgumentList buildArgumentList([Scope scope]) { + final allArguments = []; + allArguments.addAll( + _positional.map/**/((e) => e.buildExpression(scope))); + _named.forEach((name, e) { + allArguments.add(new NamedExpression( + new Label( + stringIdentifier(name), + $colon, + ), + e.buildExpression(scope), + )); + }); + return new ArgumentList( + $openParen, + allArguments, + $closeParen, + ); + } + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); +} + +/// Builds an invocation AST. +abstract class InvocationBuilder + implements ExpressionBuilder, StatementBuilder, ValidMethodMember { + factory InvocationBuilder._(ExpressionBuilder target) { + return new _FunctionInvocationBuilder(target); + } + + factory InvocationBuilder._on(ExpressionBuilder target, String method) { + return new _MethodInvocationBuilder(target, method); + } + + /// Adds [argument] as a [name]d argument to this method call. + void addNamedArgument(String name, ExpressionBuilder argument); + + /// Adds [argument] as a positional argument to this method call. + void addPositionalArgument(ExpressionBuilder argument); +} + +class _FunctionInvocationBuilder extends Object + with AbstractInvocationBuilderMixin, AbstractExpressionMixin + implements InvocationBuilder { + final ExpressionBuilder _target; + + _FunctionInvocationBuilder(this._target); + + @override + Expression buildExpression([Scope scope]) { + return new FunctionExpressionInvocation( + _target.buildExpression(scope), + null, + buildArgumentList(scope), + ); + } +} + +class _MethodInvocationBuilder extends Object + with AbstractInvocationBuilderMixin, AbstractExpressionMixin + implements InvocationBuilder { + final String _method; + final ExpressionBuilder _target; + + _MethodInvocationBuilder(this._target, this._method); + + @override + Expression buildExpression([Scope scope]) { + return new MethodInvocation( + _target.buildExpression(scope), + $period, + stringIdentifier(_method), + null, + buildArgumentList(scope), + ); + } +} diff --git a/lib/src/builders/expression/negate.dart b/lib/src/builders/expression/negate.dart new file mode 100644 index 0000000..d1d58d3 --- /dev/null +++ b/lib/src/builders/expression/negate.dart @@ -0,0 +1,35 @@ +part of code_builder.src.builders.expression; + +class _NegateExpression extends AbstractExpressionMixin { + final ExpressionBuilder _expression; + + _NegateExpression(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new PrefixExpression( + $not, + _expression.parentheses().buildExpression(scope), + ); + } +} + +class _NegativeExpression extends AbstractExpressionMixin { + final ExpressionBuilder _expression; + + _NegativeExpression(this._expression); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new PrefixExpression( + $minus, + _expression.parentheses().buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/expression/operators.dart b/lib/src/builders/expression/operators.dart new file mode 100644 index 0000000..f36140a --- /dev/null +++ b/lib/src/builders/expression/operators.dart @@ -0,0 +1,21 @@ +part of code_builder.src.builders.expression; + +class _AsBinaryExpression extends Object with AbstractExpressionMixin { + final ExpressionBuilder _left; + final ExpressionBuilder _right; + final Token _operator; + + _AsBinaryExpression(this._left, this._right, this._operator); + + @override + AstNode buildAst([Scope scope]) => buildExpression(scope); + + @override + Expression buildExpression([Scope scope]) { + return new BinaryExpression( + _left.buildExpression(scope), + _operator, + _right.buildExpression(scope), + ); + } +} diff --git a/lib/src/builders/expression/return.dart b/lib/src/builders/expression/return.dart new file mode 100644 index 0000000..d57c7de --- /dev/null +++ b/lib/src/builders/expression/return.dart @@ -0,0 +1,19 @@ +part of code_builder.src.builders.expression; + +class _AsReturn implements StatementBuilder { + final ExpressionBuilder _value; + + _AsReturn(this._value); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new ReturnStatement( + $return, + _value.buildExpression(), + $semicolon, + ); + } +} diff --git a/lib/src/builders/expression_builder.dart b/lib/src/builders/expression_builder.dart deleted file mode 100644 index 7175c8c..0000000 --- a/lib/src/builders/expression_builder.dart +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -// Shared functionality between ExpressionBuilder and _LiteralExpression. -/// Represents an expression value of `false`. -const literalFalse = const LiteralBool(false); - -/// Represents an expression value of `null`. -const literalNull = const _LiteralNull(); - -/// Represents an expression value of `true`. -const literalTrue = const LiteralBool(true); - -// TODO(matanl): Make this part of the public API. See annotation_builder.dart. -// Returns wrapped as a [ExpressionFunctionBody] AST. -ExpressionFunctionBody _asFunctionBody( - CodeBuilder expression, - Scope scope, -) { - return new ExpressionFunctionBody( - null, - null, - expression.toAst(scope), - $semicolon, - ); -} - -// Returns wrapped as a [FunctionExpression] AST. -FunctionExpression _asFunctionExpression( - CodeBuilder expression, - Scope scope, -) { - return new FunctionExpression( - null, - new FormalParameterList( - $openParen, - const [], - null, - null, - $closeParen, - ), - _asFunctionBody(expression, scope), - ); -} - -ExpressionBuilder _invokeSelfImpl( - ExpressionBuilder self, - String name, { - Iterable> positional: const [], - Map> named: const {}, -}) { - return new _InvokeExpression.target( - name, - self, - positional, - named, - ); -} - -/// Builds an [Expression] AST. -/// -/// For simple literal expressions see: -/// - [LiteralBool] and [literalTrue] [literalFalse] -/// - [LiteralInt] -/// - [LiteralString] -/// - [literalNull] -abstract class ExpressionBuilder implements CodeBuilder { - /// Invoke [name] (which should be available in the local scope). - /// - /// May specify [positional] and [named] arguments. - factory ExpressionBuilder.invoke( - String name, { - String importFrom, - Iterable> positional: const [], - Map> named: const {}, - }) { - return new _InvokeExpression( - name, - new List>.unmodifiable(positional), - new Map>.unmodifiable(named), - importFrom, - ); - } - - /// Invoke the 'new' operator on [type]. - /// - /// May use a [name] of a constructor and [positional] and [named] arguments. - factory ExpressionBuilder.invokeNew( - TypeBuilder type, { - String name, - Iterable> positional: const [], - Map> named: const {}, - }) { - return new _InvokeExpression.newInstance( - type, - name, - new List>.unmodifiable(positional), - new Map>.unmodifiable(named), - ); - } - - /// Assign [left] to [right]. - /// - /// If [nullAware] is true, the assignment uses the `??=` operator. - factory ExpressionBuilder.assignment( - String left, CodeBuilder right, - {bool nullAware: false}) { - return new _AssignmentExpression(left, right, nullAware: nullAware); - } - - const ExpressionBuilder._(); - - /// Return a new [ExpressionBuilder] invoking the result of this expression. - ExpressionBuilder invokeSelf( - String name, { - Iterable> positional: const [], - Map> named: const {}, - }); // TODO(matanl): Rename to invoke when factory is removed. - - /// Returns wrapped as a [ExpressionFunctionBody] AST. - 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); - - /// Converts to a [StatementBuilder]. - /// - /// __Example use__: - /// literalNull.toStatement(); // ==> null; - StatementBuilder toStatement(); -} - -/// Creates a new literal `bool` value. -class LiteralBool extends _LiteralExpression { - static final BooleanLiteral _true = new BooleanLiteral($true, true); - static final BooleanLiteral _false = new BooleanLiteral($false, false); - - final bool _value; - - /// Returns the passed value as a [BooleanLiteral]. - const LiteralBool(this._value); - - @override - BooleanLiteral toAst([_]) => _value ? _true : _false; -} - -/// Represents an expression value of a literal number. -class LiteralInt extends _LiteralExpression { - final int _value; - - /// Returns the passed value as a [IntegerLiteral]. - const LiteralInt(this._value); - - @override - IntegerLiteral toAst([_]) => new IntegerLiteral( - intToken(_value), - _value, - ); -} - -/// Represents an expression value of a literal `'string'`. -class LiteralString extends _LiteralExpression { - final String _value; - - /// Returns the passed value as a [StringLiteral]. - const LiteralString(this._value); - - @override - StringLiteral toAst([_]) => new SimpleStringLiteral( - stringToken("'$_value'"), - _value, - ); -} - -class _InvokeExpression extends ExpressionBuilder { - 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) - : _importFrom = null, - _type = null, - super._(); - - @override - ExpressionBuilder invokeSelf( - String name, { - Iterable> positional: const [], - Map> named: const {}, - }) => - _invokeSelfImpl(this, name, positional: positional, named: named); - - @override - Expression toAst([Scope scope = const Scope.identity()]) { - // TODO(matanl): Move to TypeBuilder.newInstance. - if (_type != null) { - return new InstanceCreationExpression( - $new, - new ConstructorName( - _type.toAst(scope), - _name != null ? $period : null, - _name != null ? _stringIdentifier(_name) : null, - ), - _getArgumentList(scope), - ); - } - return new MethodInvocation( - _target?.toAst(scope), - _target != null ? $period : null, - _stringIdentifier(_name), - null, - _getArgumentList(scope), - ); - } - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); - - ArgumentList _getArgumentList(Scope scope) { - return new ArgumentList( - $openCurly, - _positionalArguments.map/* p.toAst(scope)).toList() - ..addAll(_namedArguments.keys - .map/**/((name) => new NamedExpression( - new Label( - _stringIdentifier(name), - $colon, - ), - _namedArguments[name].toAst(scope), - ))), - $closeCurly, - ); - } -} - -class _AssignmentExpression extends ExpressionBuilder { - final String left; - final CodeBuilder right; - final bool nullAware; - - _AssignmentExpression(this.left, this.right, {this.nullAware: false}) - : super._(); - - @override - ExpressionBuilder invokeSelf(String name, - {Iterable> positional: const [], - Map> named: const {}}) { - return _invokeSelfImpl(this, name, positional: positional, named: named); - } - - @override - Expression toAst([Scope scope = const Scope.identity()]) { - return new AssignmentExpression(_stringIdentifier(left), - nullAware ? $nullAwareEquals : $equals, right.toAst(scope)); - } - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); -} - -abstract class _LiteralExpression - implements ExpressionBuilder, CodeBuilder { - const _LiteralExpression(); - - @override - ExpressionBuilder invokeSelf( - String name, { - Iterable> positional: const [], - Map> named: const {}, - }) => - _invokeSelfImpl(this, name, positional: positional, named: named); - - @override - ExpressionFunctionBody toFunctionBody( - [Scope scope = const Scope.identity()]) => - _asFunctionBody(this, scope); - - @override - FunctionExpression toFunctionExpression( - [Scope scope = const Scope.identity()]) => - _asFunctionExpression(this, scope); - - @override - StatementBuilder toStatement() => new _ExpressionStatementBuilder(this); -} - -class _LiteralNull extends _LiteralExpression { - static final NullLiteral _null = new NullLiteral($null); - - const _LiteralNull(); - - @override - NullLiteral toAst([_]) => _null; -} diff --git a/lib/src/builders/field.dart b/lib/src/builders/field.dart new file mode 100644 index 0000000..51b5d97 --- /dev/null +++ b/lib/src/builders/field.dart @@ -0,0 +1,170 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/expression.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Short-hand for [FieldBuilder]. +FieldBuilder varField( + String name, { + TypeBuilder type, + ExpressionBuilder value, +}) { + return new FieldBuilder( + name, + type: type, + value: value, + ); +} + +/// Short-hand for [FieldBuilder.asFinal]. +FieldBuilder varFinal( + String name, { + TypeBuilder type, + ExpressionBuilder value, +}) { + return new FieldBuilder.asFinal( + name, + type: type, + value: value, + ); +} + +/// Short-hand for [FieldBuilder.asConst]. +FieldBuilder varConst( + String name, { + TypeBuilder type, + ExpressionBuilder value, +}) { + return new FieldBuilder.asConst( + name, + type: type, + value: value, + ); +} + +/// Lazily builds an field AST when builder is invoked. +abstract class FieldBuilder + implements + AstBuilder, + HasAnnotations, + StatementBuilder, + ValidClassMember { + /// Creates a new [FieldBuilder] defining a new `var`. + factory FieldBuilder( + String name, { + TypeBuilder type, + ExpressionBuilder value, + }) => + new _FieldBuilderImpl( + name, + keyword: type == null ? Keyword.VAR : null, + type: type, + value: value, + ); + + /// Creates a new [FieldBuilder] defining a new `const`. + factory FieldBuilder.asConst( + String name, { + TypeBuilder type, + ExpressionBuilder value, + }) => + new _FieldBuilderImpl( + name, + keyword: Keyword.CONST, + type: type, + value: value, + ); + + /// Creates a new [FieldBuilder] defining a new `final`. + factory FieldBuilder.asFinal( + String name, { + TypeBuilder type, + ExpressionBuilder value, + }) => + new _FieldBuilderImpl( + name, + keyword: Keyword.FINAL, + type: type, + value: value, + ); + + /// Returns as a [FieldDeclaration] AST. + FieldDeclaration buildField(bool static, [Scope scope]); + + /// Returns as a [TopLevelVariableDeclaration] AST. + TopLevelVariableDeclaration buildTopLevel([Scope scope]); +} + +class _FieldBuilderImpl extends Object + with HasAnnotationsMixin + implements FieldBuilder { + final Keyword _keyword; + final String _name; + final TypeBuilder _type; + final ExpressionBuilder _value; + + _FieldBuilderImpl( + String name, { + Keyword keyword, + TypeBuilder type, + ExpressionBuilder value, + }) + : _keyword = keyword, + _name = name, + _type = type, + _value = value; + + @override + TopLevelVariableDeclaration buildAst([Scope scope]) => buildTopLevel(scope); + + @override + FieldDeclaration buildField(bool static, [Scope scope]) { + return new FieldDeclaration( + null, + buildAnnotations(scope), + static ? $static : null, + _buildVariableList(scope), + $semicolon, + ); + } + + @override + Statement buildStatement([Scope scope]) { + return new VariableDeclarationStatement( + _buildVariableList(scope), + $semicolon, + ); + } + + @override + TopLevelVariableDeclaration buildTopLevel([Scope scope]) { + return new TopLevelVariableDeclaration( + null, + null, + _buildVariableList(scope), + $semicolon, + ); + } + + VariableDeclarationList _buildVariableList([Scope scope]) { + return new VariableDeclarationList( + null, + null, + _keyword != null ? new KeywordToken(_keyword, 0) : null, + _type?.buildType(scope), + [ + new VariableDeclaration( + stringIdentifier(_name), + _value != null ? $equals : null, + _value?.buildExpression(scope), + ) + ], + ); + } +} diff --git a/lib/src/builders/field_builder.dart b/lib/src/builders/field_builder.dart deleted file mode 100644 index 82839b1..0000000 --- a/lib/src/builders/field_builder.dart +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Builds either a [FieldDeclaration] or [VariableDeclaration] AST. -/// -/// While the API is the same, you may specifically ask for either a field -/// AST (for class members) via [toFieldAst] or a variable declaration (used -/// both at the top-level and within methods) via [toVariablesAst]. -class FieldBuilder implements CodeBuilder { - final bool _isConst; - final bool _isFinal; - final bool _isStatic; - final ExpressionBuilder _initialize; - final String _name; - final TypeBuilder _type; - - /// Create a new field builder. - /// - /// Optionally set a type and initializer. - FieldBuilder( - this._name, { - TypeBuilder type, - ExpressionBuilder initialize, - bool asStatic: false, - }) - : this._type = type, - this._initialize = initialize, - this._isFinal = false, - this._isConst = false, - this._isStatic = asStatic; - - /// Create a new field builder that emits a `const` field. - /// - /// Optionally set a type and initializer. - FieldBuilder.isConst( - this._name, { - TypeBuilder type, - ExpressionBuilder initialize, - bool asStatic: false, - }) - : this._type = type, - this._initialize = initialize, - this._isFinal = false, - this._isConst = true, - this._isStatic = false; - - /// Create a new field builder that emits a `final` field. - /// - /// Optionally set a type and initializer. - FieldBuilder.isFinal( - this._name, { - TypeBuilder type, - ExpressionBuilder initialize, - bool asStatic: false, - }) - : this._type = type, - this._initialize = initialize, - this._isFinal = true, - this._isConst = false, - this._isStatic = asStatic; - - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// **NOTE**: This method exists primarily for testing and compatibility with - /// the [CodeBuilder] ADT. When possible, invoke [toFieldAst] or - /// [toVariablesAst]. - @override - @visibleForTesting - Declaration toAst([Scope scope = const Scope.identity()]) => - toFieldAst(scope); - - /// Returns a copy-safe [FieldDeclaration] AST representing current state. - FieldDeclaration toFieldAst([Scope scope = const Scope.identity()]) => - new FieldDeclaration( - null, - null, - _isStatic ? $static : null, - toVariablesAst(scope), - null, - ); - - /// Returns a copy-safe [VariableDeclaration] AST representing current state. - VariableDeclarationList toVariablesAst( - [Scope scope = const Scope.identity()]) => - new VariableDeclarationList( - null, - null, - _getVariableKeyword(), - _type?.toAst(scope), - [ - new VariableDeclaration( - _stringIdentifier(_name), - _initialize != null ? $equals : null, - _initialize?.toAst(scope), - ) - ], - ); - - Token _getVariableKeyword() { - if (_isFinal) { - return $final; - } - if (_isConst) { - return $const; - } - return _type == null ? $var : null; - } -} diff --git a/lib/src/builders/file.dart b/lib/src/builders/file.dart new file mode 100644 index 0000000..fc6e71b --- /dev/null +++ b/lib/src/builders/file.dart @@ -0,0 +1,156 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Builds a file of Dart source code. +/// +/// See [LibraryBuilder] and [PartBuilder] for concrete implementations. +abstract class FileBuilder implements AstBuilder { + final List> _members = + >[]; + + FileBuilder._(); + + /// Adds a top-level field or class [member]. + void addMember(AstBuilder member) { + _members.add(member); + } + + /// Adds top-level field or class [members]. + void addMembers(Iterable> members) { + _members.addAll(members); + } +} + +/// Builds a standalone file (library) of Dart source code. +class LibraryBuilder extends FileBuilder { + final List> _directives = >[]; + final Scope _scope; + + /// Creates a new standalone Dart library, optionally with [name]. + factory LibraryBuilder([String name]) => + new LibraryBuilder._(name, Scope.identity); + + /// Creates a new standalone Dart library, optionally with [name]. + /// + /// Uses the default [Scope] implementation unless [scope] is set. + factory LibraryBuilder.scope({String name, Scope scope}) { + return new LibraryBuilder._(name, scope ?? new Scope()); + } + + LibraryBuilder._(String name, this._scope) : super._() { + if (name != null) { + _directives.add(new _LibraryDirectiveBuilder(name)); + } + } + + /// Adds a file [directive]. + void addDirective(AstBuilder directive) { + _directives.add(directive); + } + + /// Add file [directives]. + void addDirectives(Iterable> directives) { + _directives.addAll(directives); + } + + @override + CompilationUnit buildAst([_]) { + var members = _members.map((m) => m.buildAst(_scope)).toList(); + var directives = [] + ..addAll(_scope.toImports().map((d) => d.buildAst())) + ..addAll(_directives.map((d) => d.buildAst())); + return new CompilationUnit( + null, + null, + directives, + members, + null, + ); + } +} + +/// Lazily builds a partial file (part of) Dart source code. +class PartBuilder extends FileBuilder { + final String _name; + + /// Creates a partial Dart file. + factory PartBuilder(String name) = PartBuilder._; + + PartBuilder._(this._name) : super._(); + + @override + CompilationUnit buildAst([_]) { + return new CompilationUnit( + null, + null, + [ + new PartOfDirective( + null, + null, + $part, + $of, + new LibraryIdentifier([ + new SimpleIdentifier(stringToken(_name)), + ]), + $semicolon, + ) + ], + _members.map((m) => m.buildAst()).toList(), + null, + ); + } +} + +class _LibraryDirectiveBuilder implements AstBuilder { + final String _name; + + _LibraryDirectiveBuilder(this._name); + + @override + LibraryDirective buildAst([_]) { + return new LibraryDirective( + null, + null, + $library, + new LibraryIdentifier([ + new SimpleIdentifier( + stringToken(_name), + ), + ]), + $semicolon, + ); + } +} + +/// Lazily builds an [ImportDirective] AST when built. +class ImportBuilder implements AstBuilder { + final String _prefix; + final String _uri; + + factory ImportBuilder(String path, {String prefix}) { + return new ImportBuilder._(path, prefix); + } + + ImportBuilder._(this._uri, this._prefix); + + @override + ImportDirective buildAst([_]) { + return new ImportDirective( + null, + null, + null, + new SimpleStringLiteral(stringToken("'$_uri'"), _uri), + null, + null, + _prefix != null ? $as : null, + _prefix != null ? stringIdentifier(_prefix) : null, + null, + $semicolon, + ); + } +} diff --git a/lib/src/builders/file_builder.dart b/lib/src/builders/file_builder.dart deleted file mode 100644 index 1b386b7..0000000 --- a/lib/src/builders/file_builder.dart +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -CompilationUnit _emptyCompilationUnit() => new CompilationUnit( - null, - null, - null, - null, - null, - ); - -/// An `export` directive in a [LibraryBuilder]. -class ExportBuilder implements CodeBuilder { - final String _uri; - - /// Create a new `export` directive exporting [uri]. - factory ExportBuilder(String uri) = ExportBuilder._; - - const ExportBuilder._(this._uri); - - @override - ExportDirective toAst([_]) { - return _createExportDirective()..uri = _stringLiteral("'$_uri'"); - } - - static ExportDirective _createExportDirective() => new ExportDirective( - null, - null, - null, - null, - null, - null, - null, - ); -} - -/// Builds files of Dart source code. -/// -/// See [LibraryBuilder] and [PartBuilder] for concrete implementations. -abstract class FileBuilder implements CodeBuilder { - final List> _declarations = - >[]; - - FileBuilder._(); - - /// Adds [declaration]'s resulting AST to the source. - void addDeclaration(CodeBuilder declaration) { - _declarations.add(declaration); - } - - @override - @mustCallSuper - CompilationUnit toAst([Scope scope = const Scope.identity()]) => - _emptyCompilationUnit() - ..declarations - .addAll(_declarations.map/**/((d) => d.toAst(scope))); -} - -/// An `import` directive in a [FileBuilder]. -class ImportBuilder implements CodeBuilder { - final String _uri; - final String _prefix; - - /// Create a new `import` directive importing [uri]. - /// - /// Optionally prefix [prefix]. - const factory ImportBuilder(String uri, {String prefix}) = ImportBuilder._; - - const ImportBuilder._(this._uri, {String prefix}) : _prefix = prefix; - - @override - ImportDirective toAst([_]) => _createImportDirective() - ..uri = _stringLiteral("'$_uri'") - ..prefix = _prefix != null ? _stringIdentifier(_prefix) : null; - - static ImportDirective _createImportDirective() => new ImportDirective( - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ); -} - -/// Builds a standalone Dart library [CompilationUnit] AST. -class LibraryBuilder extends FileBuilder { - final String _name; - final Scope _scope; - - final List> _directives = >[]; - - /// Create a new standalone Dart library, optionally with a [name]. - factory LibraryBuilder([String name]) { - return new LibraryBuilder._(name, const Scope.identity()); - } - - /// Create a new standalone Dart library, optionally with a [name]. - /// - /// As references are added in the library that implements [CodeBuilder] - /// they are re-written to avoid collisions and the imports are automatically - /// included at the top with optional prefixes. - factory LibraryBuilder.scope({String name, Scope scope}) { - return new LibraryBuilder._(name, scope ?? new Scope()); - } - - LibraryBuilder._(this._name, this._scope) : super._(); - - /// Adds [directive]'s resulting AST to the source. - void addDirective(CodeBuilder directive) { - _directives.add(directive); - } - - @override - CompilationUnit toAst([_]) { - var originalAst = super.toAst(_scope); - if (_name != null) { - originalAst.directives.add( - new LibraryDirective( - null, - null, - $library, - new LibraryIdentifier([_stringIdentifier(_name)]), - null, - ), - ); - } - originalAst.directives..addAll(_directives.map((d) => d.toAst())); - originalAst.directives..addAll(_scope.getImports().map((i) => i.toAst())); - return originalAst; - } -} - -/// Builds a `part of` [CompilationUnit] AST for an existing Dart library. -class PartBuilder extends FileBuilder { - final String _name; - - /// Create a new `part of` source file. - factory PartBuilder(String name) = PartBuilder._; - - PartBuilder._(this._name) : super._(); - - @override - CompilationUnit toAst([Scope scope = const Scope.identity()]) { - var originalAst = super.toAst(); - originalAst.directives.add(new PartOfDirective( - null, - null, - $part, - $of, - new LibraryIdentifier([_stringIdentifier(_name)]), - null, - )); - return originalAst; - } -} diff --git a/lib/src/builders/method.dart b/lib/src/builders/method.dart new file mode 100644 index 0000000..2228d2f --- /dev/null +++ b/lib/src/builders/method.dart @@ -0,0 +1,446 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/class.dart'; +import 'package:code_builder/src/builders/parameter.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Short-hand for `new ConstructorBuilder(...)`. +ConstructorBuilder constructor( + [Iterable members = const []]) { + return _constructorImpl(members: members); +} + +/// Short-hand for `new ConstructorBuilder(name)`. +ConstructorBuilder constructorNamed(String name, + [Iterable members = const []]) { + return _constructorImpl(name: name, members: members); +} + +/// Short-hand for `new MethodBuilder.getter(...)`. +MethodBuilder getter( + String name, { + Iterable statements, + ExpressionBuilder returns, + TypeBuilder returnType, +}) { + if (returns != null) { + return new MethodBuilder.getter( + name, + returnType: returnType, + returns: returns, + ); + } else { + return new MethodBuilder.getter( + name, + returnType: returnType, + )..addStatements(statements); + } +} + +/// A more short-hand way of constructing a Lambda [MethodBuilder]. +MethodBuilder lambda( + String name, + ExpressionBuilder value, { + TypeBuilder returnType, +}) { + return new MethodBuilder(name, returns: value, returnType: returnType); +} + +/// A more short-hand way of constructing a [MethodBuilder]. +MethodBuilder method( + String name, [ + Iterable members = const [], +]) { + final List positional = []; + final List<_NamedParameterWrapper> named = <_NamedParameterWrapper>[]; + final List statements = []; + TypeBuilder returnType; + for (final member in members) { + if (member is TypeBuilder) { + returnType = member; + } else if (member is ParameterBuilder) { + positional.add(member); + } else if (member is _NamedParameterWrapper) { + named.add(member); + } else if (member is StatementBuilder) { + statements.add(member); + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + final method = new _MethodBuilderImpl( + name, + returns: returnType, + ); + positional.forEach(method.addPositional); + named.forEach((p) => method.addNamed(p._parameter)); + statements.forEach(method.addStatement); + return method; +} + +/// Returns a wrapper around [parameter] for use with [method]. +_NamedParameterWrapper named(ParameterBuilder parameter) { + return new _NamedParameterWrapper(parameter); +} + +/// Short-hand for `new MethodBuilder.setter(...)`. +MethodBuilder setter( + String name, + ParameterBuilder value, + Iterable statements, +) { + return new MethodBuilder.setter(name) + ..addPositional(value) + ..addStatements(statements); +} + +/// Returns a wrapper around [parameter] for use with [constructor]. +_FieldParameterWrapper thisField(Object parameter) { + assert(parameter is ParameterBuilder || parameter is _NamedParameterWrapper); + return new _FieldParameterWrapper(parameter); +} + +ConstructorBuilder _constructorImpl({ + Iterable members, + String name, +}) { + final List<_AddParameter> _addFunctions = <_AddParameter>[]; + for (final member in members) { + if (member is ParameterBuilder) { + _addFunctions.add((c) => c.addPositional(member)); + } else if (member is _NamedParameterWrapper) { + _addFunctions.add((c) => c.addNamed(member._parameter)); + } else if (member is _FieldParameterWrapper) { + if (member._parameter is _NamedParameterWrapper) { + _NamedParameterWrapper p = member._parameter; + _addFunctions.add((c) => c.addNamed(p._parameter, asField: true)); + } else if (member._parameter is ParameterBuilder) { + _addFunctions + .add((c) => c.addPositional(member._parameter, asField: true)); + } + } else if (member is StatementBuilder) { + _addFunctions.add((c) => c.addStatement(member)); + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + final constructor = new ConstructorBuilder(name); + _addFunctions.forEach((a) => a(constructor)); + return constructor; +} + +typedef void _AddParameter(ConstructorBuilder constructor); + +/// Lazily builds an [ConstructorBuilder] AST when built. +abstract class ConstructorBuilder + implements + AstBuilder, + HasParameters, + HasStatements, + ValidClassMember { + /// Create a new [ConstructorBuilder], optionally with a [name]. + factory ConstructorBuilder([String name]) = _NormalConstructorBuilder; + + @override + void addNamed(ParameterBuilder parameter, {bool asField: false}); + + @override + void addPositional(ParameterBuilder parameter, {bool asField: false}); + + /// Returns an [ConstructorDeclaration] AST representing the builder. + ConstructorDeclaration buildConstructor(TypeBuilder returnType, + [Scope scope]); +} + +/// Lazily builds a method/function AST when the builder is invoked. +abstract class MethodBuilder + implements HasAnnotations, HasParameters, HasStatements, ValidClassMember { + /// Creates a new [MethodBuilder]. + factory MethodBuilder(String name, + {ExpressionBuilder returns, TypeBuilder returnType}) { + if (returns != null) { + return new _LambdaMethodBuilder( + name, + returns, + returnType, + null, + ); + } else { + return new _MethodBuilderImpl( + name, + returns: returnType, + ); + } + } + + /// Creates a getter. + factory MethodBuilder.getter( + String name, { + TypeBuilder returnType, + ExpressionBuilder returns, + }) { + if (returns == null) { + return new _MethodBuilderImpl( + name, + returns: returnType, + property: Keyword.GET, + ); + } else { + return new _LambdaMethodBuilder( + name, + returns, + returnType, + Keyword.GET, + ); + } + } + + /// Creates a new [MethodBuilder] that returns `void`. + factory MethodBuilder.returnVoid(String name, {ExpressionBuilder returns}) { + if (returns == null) { + return new _MethodBuilderImpl(name, returns: lib$core.$void); + } + return new _LambdaMethodBuilder( + name, + returns, + lib$core.$void, + null, + ); + } + + /// Creates a setter. + factory MethodBuilder.setter( + String name, { + ExpressionBuilder returns, + }) { + if (returns == null) { + return new _MethodBuilderImpl( + name, + property: Keyword.SET, + ); + } else { + return new _LambdaMethodBuilder( + name, + returns, + null, + Keyword.SET, + ); + } + } + + /// Returns a [FunctionDeclaration] AST representing the builder. + FunctionDeclaration buildFunction([Scope scope]); + + /// Returns an [MethodDeclaration] AST representing the builder. + MethodDeclaration buildMethod(bool static, [Scope scope]); +} + +/// A marker interface for an AST that could be added to [ConstructorBuilder]. +abstract class ValidConstructorMember implements ValidMethodMember {} + +/// A marker interface for an AST that could be added to [MethodBuilder]. +abstract class ValidMethodMember implements AstBuilder {} + +class _FieldParameterWrapper + implements ValidConstructorMember, ValidMethodMember { + final Object /*ParameterBuilder|_NamedParameterWrapper*/ _parameter; + + _FieldParameterWrapper(this._parameter); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); +} + +class _LambdaMethodBuilder extends Object + with HasAnnotationsMixin, HasParametersMixin + implements MethodBuilder { + final ExpressionBuilder _expression; + final String _name; + final TypeBuilder _returnType; + final Keyword _property; + + _LambdaMethodBuilder( + this._name, this._expression, this._returnType, this._property); + + @override + void addStatement(StatementBuilder statement) { + throw new UnsupportedError('Cannot add statement on a Lambda method'); + } + + @override + void addStatements(Iterable statements) { + throw new UnsupportedError('Cannot add statement on a Lambda method'); + } + + @override + AstNode buildAst([Scope scope]) => buildFunction(scope); + + @override + FunctionDeclaration buildFunction([Scope scope]) { + return new FunctionDeclaration( + null, + buildAnnotations(scope), + null, + _returnType?.buildType(scope), + _property != null ? new KeywordToken(_property, 0) : null, + identifier(scope, _name), + new FunctionExpression( + null, + _property != Keyword.GET ? buildParameterList(scope) : null, + new ExpressionFunctionBody( + null, + null, + _expression.buildExpression(scope), + $semicolon, + ), + ), + ); + } + + @override + MethodDeclaration buildMethod(bool static, [Scope scope]) { + return new MethodDeclaration( + null, + buildAnnotations(scope), + null, + static ? $static : null, + _returnType?.buildType(scope), + _property != null ? new KeywordToken(_property, 0) : null, + null, + stringIdentifier(_name), + null, + _property != Keyword.GET ? buildParameterList(scope) : null, + new ExpressionFunctionBody( + null, + null, + _expression.buildExpression(scope), + $semicolon, + ), + ); + } +} + +class _MethodBuilderImpl extends Object + with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin + implements MethodBuilder { + final String _name; + final TypeBuilder _returnType; + final Keyword _property; + + _MethodBuilderImpl( + this._name, { + TypeBuilder returns, + Keyword property, + }) + : _returnType = returns, + _property = property; + + @override + AstNode buildAst([Scope scope]) => buildFunction(scope); + + @override + FunctionDeclaration buildFunction([Scope scope]) { + return new FunctionDeclaration( + null, + buildAnnotations(scope), + null, + _returnType?.buildType(scope), + _property != null ? new KeywordToken(_property, 0) : null, + identifier(scope, _name), + new FunctionExpression( + null, + _property != Keyword.GET ? buildParameterList(scope) : null, + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + buildBlock(scope), + ), + ), + ); + } + + @override + MethodDeclaration buildMethod(bool static, [Scope scope]) { + return new MethodDeclaration( + null, + buildAnnotations(scope), + null, + static ? $static : null, + _returnType?.buildType(scope), + _property != null ? new KeywordToken(_property, 0) : null, + null, + identifier(scope, _name), + null, + _property != Keyword.GET ? buildParameterList(scope) : null, + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + buildBlock(scope), + ), + ); + } +} + +class _NamedParameterWrapper + implements ValidConstructorMember, ValidMethodMember { + final ParameterBuilder _parameter; + + _NamedParameterWrapper(this._parameter); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within method'); +} + +class _NormalConstructorBuilder extends Object + with HasAnnotationsMixin, HasParametersMixin, HasStatementsMixin + implements ConstructorBuilder { + final String _name; + + _NormalConstructorBuilder([this._name]); + + @override + ConstructorDeclaration buildAst([Scope scope]) { + throw new UnsupportedError('Can only be built as part of a class.'); + } + + @override + ConstructorDeclaration buildConstructor(TypeBuilder returnType, + [Scope scope]) { + return new ConstructorDeclaration( + null, + buildAnnotations(scope), + null, + null, + null, + returnType.buildType().name, + _name != null ? $period : null, + _name != null ? stringIdentifier(_name) : null, + buildParameterList(scope), + null, + null, + null, + !hasStatements + ? new EmptyFunctionBody($semicolon) + : new BlockFunctionBody( + null, + null, + buildBlock(scope), + ), + ); + } +} diff --git a/lib/src/builders/method_builder.dart b/lib/src/builders/method_builder.dart deleted file mode 100644 index 252c836..0000000 --- a/lib/src/builders/method_builder.dart +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Builds either a [MethodDeclaration] or a [FunctionDeclaration]. -/// -/// While the API is the same, you may specifically ask for either a method -/// AST (for class members) via [toMethodAst] or a function AST (used both at -/// the top level and within other methods) via [toFunctionAst]. -/// -/// To return nothing (`void`), use [MethodBuilder.returnVoid]. -class MethodBuilder implements CodeBuilder { - // Void is a "type" that is only valid as a return type on a method. - static const TypeBuilder _typeVoid = const TypeBuilder('void'); - - final List _annotations = []; - final String _name; - final List _parameters = []; - final List _statements = []; - - final bool _isAbstract; - final bool _isStatic; - - ExpressionBuilder _returnExpression; - TypeBuilder _returnType; - - /// Create a new method builder, - /// - /// Optionally set a [returns] type. - factory MethodBuilder({ - String name, - TypeBuilder returns, - bool abstract: false, - bool static: false, - }) { - return new MethodBuilder._(name, returns, static, abstract); - } - - /// Creates a `void`-returning MethodBuilder with [name]. - factory MethodBuilder.returnVoid({ - String name, - bool abstract: false, - bool static: false, - }) { - return new MethodBuilder._(name, _typeVoid, static, abstract); - } - - MethodBuilder._( - this._name, - this._returnType, - this._isStatic, - this._isAbstract, - ); - - /// Lazily adds [annotation]. - /// - /// When the method is emitted as an AST, [AnnotationBuilder.toAst] is used. - void addAnnotation(AnnotationBuilder annotation) { - _annotations.add(annotation); - } - - /// Lazily adds [parameter]. - /// - /// When the method is emitted as an AST, [ParameterBuilder.toAst] is used. - void addParameter(ParameterBuilder parameter) { - _parameters.add(parameter); - } - - /// Lazily adds [statement]. - /// - /// When the method is emitted as an AST, [StatementBuilder.toAst] is used. - void addStatement(StatementBuilder statement) { - _statements.add(statement); - } - - /// Lazily sets [expression] as the lambda result of this method invocation. - /// - /// When the method is emitted as an AST, [ExpressionBuilder.toAst] is used. - void setExpression(ExpressionBuilder expression) { - _returnExpression = expression; - } - - /// Returns a copy-safe [AstNode] representing the current builder state. - /// - /// **NOTE**: This method exists primarily for testing and compatibility with - /// the [CodeBuilder] ADT. When possible, invoke [toFunctionAst] or - /// [toMethodAst]. - @override - @visibleForTesting - Declaration toAst([Scope scope = const Scope.identity()]) => - toFunctionAst(scope); - - /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - FunctionDeclaration toFunctionAst([Scope scope = const Scope.identity()]) { - var functionAst = _emptyFunction() - ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) - ..name = _stringIdentifier(_name) - ..returnType = _returnType?.toAst(scope); - if (_returnExpression != null) { - functionAst.functionExpression = _returnExpression.toFunctionExpression(); - } else { - functionAst.functionExpression = new FunctionExpression( - null, - _emptyParameters(), - _blockBody(_statements.map/**/((s) => s.toAst(scope))), - ); - } - if (_parameters.isNotEmpty) { - functionAst.functionExpression.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toAst(scope))); - } - return functionAst; - } - - /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - MethodDeclaration toMethodAst([Scope scope = const Scope.identity()]) { - var methodAst = _emptyMethod() - ..metadata.addAll(_annotations.map/**/((a) => a.toAst(scope))) - ..name = _stringIdentifier(_name) - ..returnType = _returnType?.toAst(scope); - FunctionBody methodBody = _returnExpression?.toFunctionBody(scope); - if (_isStatic) { - methodAst.modifierKeyword = $static; - if (methodBody == null) { - methodBody = _blockBody(); - } - } - if (methodBody == null) { - methodBody = _isAbstract - ? new EmptyFunctionBody($semicolon) - : _blockBody(_statements.map/**/((s) => s.toAst(scope))); - } - if (_parameters.isNotEmpty) { - methodAst.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toAst(scope))); - } - methodAst.body = methodBody; - return methodAst; - } - - @override - String toString() => 'MethodBuilder ${toAst().toSource()}'; - - static FunctionBody _blockBody([Iterable statements]) => - new BlockFunctionBody( - null, - null, - new Block( - $openCurly, - statements?.toList(), - $closeCurly, - ), - ); - - static FunctionDeclaration _emptyFunction() => new FunctionDeclaration( - null, - null, - null, - null, - null, - null, - new FunctionExpression( - null, - _emptyParameters(), - _blockBody(), - ), - ); - // TODO: implement requiredImports - static MethodDeclaration _emptyMethod() => new MethodDeclaration( - null, - null, - null, - null, - null, - null, - null, - null, - null, - _emptyParameters(), - null, - ); - - static FormalParameterList _emptyParameters() => new FormalParameterList( - $openParen, - [], - null, - null, - $closeParen, - ); -} diff --git a/lib/src/builders/parameter.dart b/lib/src/builders/parameter.dart new file mode 100644 index 0000000..58df28e --- /dev/null +++ b/lib/src/builders/parameter.dart @@ -0,0 +1,213 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// A short-hand way of constructing a [ParameterBuilder]. +ParameterBuilder parameter( + String name, [ + Iterable members = const [], +]) { + final List annotations = []; + ExpressionBuilder defaultTo; + bool defaultToSet = false; + TypeBuilder type; + for (final member in members) { + if (member is TypeBuilder) { + type = member; + } else if (member is AnnotationBuilder) { + annotations.add(member); + } else if (member is ExpressionBuilder) { + defaultTo = member; + defaultToSet = true; + } else { + throw new StateError('Invalid AST type: ${member.runtimeType}'); + } + } + var builder = new ParameterBuilder( + name, + type: type, + )..addAnnotations(annotations); + if (defaultToSet) { + builder = builder.asOptional(defaultTo); + } + return builder; +} + +/// An [AstBuilder] that can be built up using [ParameterBuilder]. +abstract class HasParameters implements AstBuilder { + /// Adds [parameter] to the builder. + void addNamed(ParameterBuilder parameter); + + /// Adds [parameter] to the builder. + void addPositional(ParameterBuilder parameter); +} + +/// Implements [HasParameters]. +abstract class HasParametersMixin implements HasParameters { + final List<_ParameterPair> _parameters = <_ParameterPair>[]; + + @override + void addNamed(ParameterBuilder parameter, {bool asField: false}) { + _parameters.add(new _ParameterPair.named(parameter, field: asField)); + } + + @override + void addPositional(ParameterBuilder parameter, {bool asField: false}) { + _parameters.add(new _ParameterPair(parameter, field: asField)); + } + + /// Builds a [FormalParameterList]. + FormalParameterList buildParameterList([Scope scope]) { + return new FormalParameterList( + $openParen, + _parameters + .map/**/((p) => p.buildParameter(scope)) + .toList(), + null, + null, + $closeParen, + ); + } +} + +/// Lazily builds an [FormalParameter] AST the builder is invoked. +abstract class ParameterBuilder + implements + AstBuilder, + HasAnnotations, + ValidConstructorMember, + ValidMethodMember { + /// Create a new builder for parameter [name]. + factory ParameterBuilder( + String name, { + TypeBuilder type, + }) = _SimpleParameterBuilder; + + /// Returns as an optional [ParameterBuilder] set to [defaultTo]. + ParameterBuilder asOptional([ExpressionBuilder defaultTo]); + + /// Returns a positional [FormalParameter] AST representing the builder. + FormalParameter buildNamed(bool field, [Scope scope]); + + /// Returns a positional [FormalParameter] AST representing the builder. + FormalParameter buildPositional(bool field, [Scope scope]); +} + +/// A marker interface for an AST that could be added to [ParameterBuilder]. +abstract class ValidParameterMember implements AstBuilder {} + +class _OptionalParameterBuilder extends Object + with HasAnnotationsMixin + implements ParameterBuilder { + final ParameterBuilder _parameter; + final ExpressionBuilder _expression; + + _OptionalParameterBuilder(this._parameter, [this._expression]); + + @override + ParameterBuilder asOptional([ExpressionBuilder defaultTo]) { + return new _OptionalParameterBuilder(_parameter, defaultTo); + } + + @override + FormalParameter buildAst([Scope scope]) => buildPositional(false, scope); + + @override + FormalParameter buildNamed(bool field, [Scope scope]) { + return new DefaultFormalParameter( + _parameter.buildPositional(field, scope), + ParameterKind.NAMED, + _expression != null ? $colon : null, + _expression?.buildExpression(scope), + ); + } + + @override + FormalParameter buildPositional(bool field, [Scope scope]) { + return new DefaultFormalParameter( + _parameter.buildPositional(field, scope), + ParameterKind.POSITIONAL, + _expression != null ? $equals : null, + _expression?.buildExpression(scope), + ); + } +} + +class _ParameterPair { + final bool _isField; + final bool _isNamed; + final ParameterBuilder _parameter; + + _ParameterPair(this._parameter, {bool field: false}) + : _isNamed = false, + _isField = field; + + _ParameterPair.named(this._parameter, {bool field: false}) + : _isNamed = true, + _isField = field; + + FormalParameter buildParameter([Scope scope]) { + return _isNamed + ? _parameter.buildNamed(_isField, scope) + : _parameter.buildPositional(_isField, scope); + } +} + +class _SimpleParameterBuilder extends Object + with HasAnnotationsMixin + implements ParameterBuilder { + final String _name; + final TypeBuilder _type; + + _SimpleParameterBuilder( + String name, { + TypeBuilder type, + }) + : _name = name, + _type = type; + + @override + ParameterBuilder asOptional([ExpressionBuilder defaultTo]) { + return new _OptionalParameterBuilder(this, defaultTo); + } + + @override + FormalParameter buildAst([Scope scope]) => buildPositional(false, scope); + + @override + FormalParameter buildNamed(bool field, [Scope scope]) { + return asOptional().buildNamed(field, scope); + } + + @override + FormalParameter buildPositional(bool field, [Scope scope]) { + if (field) { + return new FieldFormalParameter( + null, + buildAnnotations(scope), + null, + _type?.buildType(scope), + $this, + $period, + stringIdentifier(_name), + null, + null, + ); + } + return new SimpleFormalParameter( + null, + buildAnnotations(scope), + null, + _type?.buildType(scope), + stringIdentifier(_name), + ); + } +} diff --git a/lib/src/builders/parameter_builder.dart b/lib/src/builders/parameter_builder.dart deleted file mode 100644 index 9f38cd4..0000000 --- a/lib/src/builders/parameter_builder.dart +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Build a [FormalParameter] AST. -/// -/// A parameter is part of a built [MethodBuilder], and can refer to a -/// class-member (field) variable (see the `field` property in the -/// constructors). -class ParameterBuilder implements CodeBuilder { - final String _name; - final bool _isField; - final bool _isOptional; - final bool _isNamed; - final TypeBuilder _type; - final ExpressionBuilder _defaultTo; - - final List _annotations = []; - - /// Create a new _required_ parameter of [name]. - factory ParameterBuilder( - String name, { - bool field: false, - TypeBuilder type, - }) { - return new ParameterBuilder._( - name, - field, - false, - false, - type, - null, - ); - } - - /// Create a new _optional_ _named_ parameter. - factory ParameterBuilder.named( - String name, { - bool field: false, - TypeBuilder type, - ExpressionBuilder defaultTo, - }) { - return new ParameterBuilder._optional( - name, - true, - field: field, - type: type, - defaultTo: defaultTo, - ); - } - - /// Create a new _optional_ (but positional) parameter. - /// - /// See [ParameterBuilder.named] for an optional _named_ parameter. - factory ParameterBuilder.optional( - String name, { - bool field: false, - TypeBuilder type, - ExpressionBuilder defaultTo, - }) { - return new ParameterBuilder._optional( - name, - false, - field: field, - type: type, - defaultTo: defaultTo, - ); - } - - ParameterBuilder._( - this._name, - this._isField, - this._isOptional, - this._isNamed, - this._type, - this._defaultTo, - ); - - // Actual implementation of optional and named. - factory ParameterBuilder._optional( - String name, - bool named, { - bool field: false, - TypeBuilder type, - ExpressionBuilder defaultTo, - }) { - return new ParameterBuilder._( - name, - field, - true, - named, - type, - defaultTo, - ); - } - - /// Adds a metadata annotation from [builder]. - void addAnnotation(AnnotationBuilder builder) { - _annotations.add(builder); - } - - @override - FormalParameter toAst([Scope scope = const Scope.identity()]) { - FormalParameter astNode; - if (_isField) { - astNode = _createFieldFormalParameter(scope); - } else { - astNode = _createSimpleFormalParameter(scope); - } - if (_isOptional) { - astNode = _createDefaultFormalParameter( - astNode, - _isNamed, - _defaultTo, - scope, - ); - } - astNode.metadata.addAll(_annotations.map/**/((a) => a.toAst())); - return astNode; - } - - FieldFormalParameter _createFieldFormalParameter(Scope scope) => - new FieldFormalParameter( - null, - null, - null, - _type?.toAst(scope), - $this, - null, - _stringIdentifier(_name), - null, - null, - ); - - SimpleFormalParameter _createSimpleFormalParameter(Scope scope) => - new SimpleFormalParameter( - null, - null, - null, - _type?.toAst(scope), - _stringIdentifier(_name), - ); - - static DefaultFormalParameter _createDefaultFormalParameter( - FormalParameter parameter, - bool named, - ExpressionBuilder defaultTo, - Scope scope, - ) { - return new DefaultFormalParameter( - parameter, - named ? ParameterKind.NAMED : ParameterKind.POSITIONAL, - defaultTo != null ? named ? $colon : $equals : null, - defaultTo?.toAst(), - ); - } -} diff --git a/lib/src/builders/reference.dart b/lib/src/builders/reference.dart new file mode 100644 index 0000000..31be042 --- /dev/null +++ b/lib/src/builders/reference.dart @@ -0,0 +1,56 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/expression.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/type.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// An explicit reference to `this`. +final ReferenceBuilder explicitThis = reference('this'); + +/// Creates a reference called [name]. +ReferenceBuilder reference(String name, [String importUri]) { + return new ReferenceBuilder._(name, importUri); +} + +/// An abstract way of representing other types of [AstBuilder]. +class ReferenceBuilder extends Object + with AbstractExpressionMixin, AbstractTypeBuilderMixin + implements AnnotationBuilder, ExpressionBuilder, TypeBuilder { + final String _importFrom; + final String _name; + + ReferenceBuilder._(this._name, [this._importFrom]); + + @override + Annotation buildAnnotation([Scope scope]) { + return new Annotation( + $at, + stringIdentifier(_name), + null, + null, + null, + ); + } + + @override + AstNode buildAst([Scope scope]) => throw new UnimplementedError(); + + @override + Expression buildExpression([Scope scope]) { + return identifier( + scope, + _name, + _importFrom, + ); + } + + @override + TypeName buildType([Scope scope]) { + return new TypeBuilder(_name, _importFrom).buildType(scope); + } +} diff --git a/lib/src/builders/shared.dart b/lib/src/builders/shared.dart new file mode 100644 index 0000000..3c16264 --- /dev/null +++ b/lib/src/builders/shared.dart @@ -0,0 +1,27 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/scope.dart'; +import 'package:code_builder/src/tokens.dart'; + +export 'package:code_builder/src/scope.dart'; + +/// Returns a string [Literal] from [value]. +Identifier stringIdentifier(String value) => + new SimpleIdentifier(stringToken(value)); + +/// Lazily builds an analyzer [AstNode] when [buildAst] is invoked. +/// +/// Most builders should also have specific typed methods for returning their +/// type of AST node, such as `buildExpression` for returning an `Expression` +/// AST. +abstract class AstBuilder { + /// Returns an [AstNode] representing the state of the current builder. + /// + /// If [scope] is provided then identifiers are automatically prefixed and + /// imports are collected in order to emit a final File AST that does not + /// have conflicting or missing imports. + T buildAst([Scope scope]); +} diff --git a/lib/src/builders/statement.dart b/lib/src/builders/statement.dart new file mode 100644 index 0000000..d12b048 --- /dev/null +++ b/lib/src/builders/statement.dart @@ -0,0 +1,71 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement/if.dart'; +import 'package:code_builder/src/tokens.dart'; + +export 'package:code_builder/src/builders/statement/if.dart' + show IfStatementBuilder, elseIf, elseThen, ifThen; + +/// An [AstBuilder] that can add [StatementBuilder]. +abstract class HasStatements implements AstBuilder { + /// Adds [statement] to the builder. + void addStatement(StatementBuilder statement); + + /// Adds [statements] to the builder. + void addStatements(Iterable statements); +} + +/// Implements [HasStatements]. +abstract class HasStatementsMixin implements HasStatements { + final List _statements = []; + + @override + void addStatement(StatementBuilder statement) { + _statements.add(statement); + } + + @override + void addStatements(Iterable statements) { + _statements.addAll(statements); + } + + /// Returns a [Block] statement. + Block buildBlock([Scope scope]) { + return new Block( + $openCurly, + buildStatements(scope), + $closeCurly, + ); + } + + /// Returns a [List] of all built [Statement]s. + List buildStatements([Scope scope]) { + return _statements + .map/**/((e) => e.buildStatement(scope)) + .toList(); + } + + /// Clones all expressions to [clone]. + void cloneStatementsTo(HasStatements clone) { + clone.addStatements(_statements); + } + + /// Whether at least one statement was added. + bool get hasStatements => _statements.isNotEmpty; +} + +/// Lazily builds an [Statement] AST when [buildStatement] is invoked. +abstract class StatementBuilder + implements + AstBuilder, + ValidIfStatementMember, + ValidConstructorMember, + ValidMethodMember { + /// Returns an [Statement] AST representing the builder. + Statement buildStatement([Scope scope]); +} diff --git a/lib/src/builders/statement/block.dart b/lib/src/builders/statement/block.dart new file mode 100644 index 0000000..57ef9eb --- /dev/null +++ b/lib/src/builders/statement/block.dart @@ -0,0 +1,27 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Represents a series of [StatementBuilder]s as a block statement AST. +abstract class BlockStatementBuilder + implements HasStatements, StatementBuilder { + /// Creates a new [BlockStatementBuilder]. + factory BlockStatementBuilder() = _BlockStatementBuilder; +} + +class _BlockStatementBuilder extends Object + with HasStatementsMixin + implements BlockStatementBuilder { + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new Block( + $openCurly, + buildStatements(scope), + $closeCurly, + ); + } +} diff --git a/lib/src/builders/statement/if.dart b/lib/src/builders/statement/if.dart new file mode 100644 index 0000000..ad3b9a9 --- /dev/null +++ b/lib/src/builders/statement/if.dart @@ -0,0 +1,95 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:code_builder/src/builders/expression.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/builders/statement.dart'; +import 'package:code_builder/src/builders/statement/block.dart'; +import 'package:code_builder/src/tokens.dart'; + +/// Denotes an [ifStmt] that should be added as an `else if` in [ifThen]. +_IfStatementBuilderWrapper elseIf(IfStatementBuilder ifStmt) { + return new _IfStatementBuilderWrapper(ifStmt, null); +} + +/// Denotes a series of [statements] added to a final `else` in [ifThen]. +_IfStatementBuilderWrapper elseThen(Iterable statements) { + return new _IfStatementBuilderWrapper(null, statements); +} + +/// Short-hand syntax for `new IfStatementBuilder(...)`. +IfStatementBuilder ifThen( + ExpressionBuilder condition, [ + Iterable members = const [], +]) { + final ifStmt = new IfStatementBuilder(condition); + IfStatementBuilder current = ifStmt; + for (final member in members) { + if (member is _IfStatementBuilderWrapper) { + if (member._ifStmt != null) { + current.setElse(member._ifStmt); + current = member._ifStmt; + } else { + current.setElse( + new BlockStatementBuilder()..addStatements(member._elseStmts)); + current = null; + } + } else if (member is StatementBuilder) { + ifStmt.addStatement(member); + } else { + throw new UnsupportedError('Invalid type: ${member.runtimeType}'); + } + } + return ifStmt; +} + +/// Builds an [IfStatement] AST. +abstract class IfStatementBuilder implements HasStatements, StatementBuilder { + /// Returns a new [IfStatementBuilder] where `if (condition) {`. + factory IfStatementBuilder(ExpressionBuilder condition) { + return new _BlockIfStatementBuilder(condition); + } + + /// Adds an `else` block that evaluates [statements]. + void setElse(StatementBuilder statements); +} + +/// Marker interface for builders valid for use with [ifThen]. +abstract class ValidIfStatementMember implements AstBuilder {} + +class _BlockIfStatementBuilder extends HasStatementsMixin + implements IfStatementBuilder { + final ExpressionBuilder _condition; + StatementBuilder _elseBlock; + + _BlockIfStatementBuilder(this._condition); + + @override + AstNode buildAst([Scope scope]) => buildStatement(scope); + + @override + Statement buildStatement([Scope scope]) { + return new IfStatement( + $if, + $openParen, + _condition.buildExpression(scope), + $closeParen, + buildBlock(scope), + _elseBlock != null ? $else : null, + _elseBlock?.buildStatement(scope), + ); + } + + @override + void setElse(StatementBuilder statements) { + _elseBlock = statements; + } +} + +class _IfStatementBuilderWrapper implements ValidIfStatementMember { + final IfStatementBuilder _ifStmt; + final Iterable _elseStmts; + + _IfStatementBuilderWrapper(this._ifStmt, this._elseStmts); + + @override + AstNode buildAst([_]) => throw new UnsupportedError('Use within ifThen.'); +} diff --git a/lib/src/builders/statement_builder.dart b/lib/src/builders/statement_builder.dart deleted file mode 100644 index 683753d..0000000 --- a/lib/src/builders/statement_builder.dart +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Builds a [Statement] AST. -abstract class StatementBuilder implements CodeBuilder { - StatementBuilder._sealed(); -} - -class _ExpressionStatementBuilder implements StatementBuilder { - final ExpressionBuilder _expression; - - _ExpressionStatementBuilder(this._expression); - - @override - Statement toAst([Scope scope = const Scope.identity()]) { - return new ExpressionStatement( - _expression.toAst(scope), - $semicolon, - ); - } -} diff --git a/lib/src/builders/type.dart b/lib/src/builders/type.dart new file mode 100644 index 0000000..c4eae10 --- /dev/null +++ b/lib/src/builders/type.dart @@ -0,0 +1,99 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library code_builder.src.builders.type; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:analyzer/src/dart/ast/token.dart'; +import 'package:code_builder/src/builders/annotation.dart'; +import 'package:code_builder/src/builders/expression.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/src/builders/parameter.dart'; +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; + +part 'type/new_instance.dart'; + +/// Implements the `new` and `const` constructor calls. +abstract class AbstractTypeBuilderMixin { + /// Invokes `const` on this type. + NewInstanceBuilder constInstance( + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._const(this); + _addArguments(builder, positional, named); + return builder; + } + + /// Invokes `const` on this type with a [name]d constructor. + NewInstanceBuilder namedConstInstance( + String name, + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._const(this, name); + _addArguments(builder, positional, named); + return builder; + } + + /// Invokes `new` on this type. + NewInstanceBuilder newInstance( + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._new(this); + _addArguments(builder, positional, named); + return builder; + } + + /// Invokes `new` on this type with a [name]d constructor. + NewInstanceBuilder namedNewInstance( + String name, + Iterable positional, [ + Map named = const {}, + ]) { + final builder = new NewInstanceBuilder._new(this, name); + _addArguments(builder, positional, named); + return builder; + } + + static void _addArguments( + NewInstanceBuilder builder, + Iterable positional, + Map named, + ) { + positional.forEach(builder.addPositionalArgument); + named.forEach(builder.addNamedArgument); + } +} + +/// Lazily builds an [TypeName] AST when [buildType] is invoked. +class TypeBuilder extends Object + with AbstractTypeBuilderMixin + implements AstBuilder, ValidMethodMember, ValidParameterMember { + final String _importFrom; + final String _name; + + /// Creates a new [TypeBuilder]. + factory TypeBuilder(String name, [String importFrom]) = TypeBuilder._; + + TypeBuilder._(this._name, [this._importFrom]); + + @override + AstNode buildAst([Scope scope]) => buildType(scope); + + /// Returns an [TypeName] AST representing the builder. + TypeName buildType([Scope scope]) { + return new TypeName( + identifier( + scope, + _name, + _importFrom, + ), + null, + ); + } +} diff --git a/lib/src/builders/type/new_instance.dart b/lib/src/builders/type/new_instance.dart new file mode 100644 index 0000000..c2d9b68 --- /dev/null +++ b/lib/src/builders/type/new_instance.dart @@ -0,0 +1,53 @@ +part of code_builder.src.builders.type; + +/// Lazily builds an [InstanceCreationExpression] AST when built. +/// +/// See [TypeBuilder]: +/// - [TypeBuilder.constInstance] +/// - [TypeBuilder.namedConstInstance] +/// - [TypeBuilder.newInstance] +/// - [TypeBuilder.namedNewInstance] +abstract class NewInstanceBuilder + implements AnnotationBuilder, InvocationBuilder { + factory NewInstanceBuilder._const(TypeBuilder type, [String name]) { + return new _NewInvocationBuilderImpl(Keyword.CONST, type, name); + } + + factory NewInstanceBuilder._new(TypeBuilder type, [String name]) { + return new _NewInvocationBuilderImpl(Keyword.NEW, type, name); + } +} + +class _NewInvocationBuilderImpl extends Object + with AbstractExpressionMixin, AbstractInvocationBuilderMixin + implements NewInstanceBuilder { + final String _name; + final Keyword _keyword; + final TypeBuilder _type; + + _NewInvocationBuilderImpl(this._keyword, this._type, [this._name]); + + @override + Annotation buildAnnotation([Scope scope]) { + return new Annotation( + $at, + _type.buildType(scope).name, + $period, + _name != null ? stringIdentifier(_name) : null, + buildArgumentList(scope), + ); + } + + @override + Expression buildExpression([Scope scope]) { + return new InstanceCreationExpression( + new KeywordToken(_keyword, 0), + new ConstructorName( + _type.buildType(scope), + _name != null ? $period : null, + _name != null ? stringIdentifier(_name) : null, + ), + buildArgumentList(scope), + ); + } +} diff --git a/lib/src/builders/type_builder.dart b/lib/src/builders/type_builder.dart deleted file mode 100644 index cff6f2f..0000000 --- a/lib/src/builders/type_builder.dart +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -part of code_builder; - -/// Build a [TypeName] AST. -class TypeBuilder implements CodeBuilder { - final String _identifier; - final String _importFrom; - - /// Create a builder for emitting a [TypeName] AST. - /// - /// Optionally specify what must be imported for this type to resolve. - /// - /// __Example use__: - /// const TypeBuilder('String', importFrom: 'dart:core') - const TypeBuilder(this._identifier, {String importFrom}) - : _importFrom = importFrom; - - @override - TypeName toAst([Scope scope = const Scope.identity()]) { - return new TypeName(scope.getIdentifier(_identifier, _importFrom), null); - } -} diff --git a/lib/src/pretty_printer.dart b/lib/src/pretty_printer.dart index e1036e0..53d3324 100644 --- a/lib/src/pretty_printer.dart +++ b/lib/src/pretty_printer.dart @@ -2,7 +2,22 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -part of code_builder; +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/dart/ast/token.dart'; +import 'package:dart_style/dart_style.dart'; + +import 'analyzer_patch.dart'; + +final _dartFmt = new DartFormatter(); + +/// Returns [source] formatted by `dartfmt`. +String dartfmt(String source) { + try { + return _dartFmt.format(source); + } on FormatterException catch (_) { + return _dartFmt.formatStatement(source); + } +} /// Augments [AstNode.toSource] by adding some whitespace/line breaks. /// @@ -14,13 +29,16 @@ String prettyToSource(AstNode astNode) { var buffer = new PrintBuffer(); var visitor = new _PrettyToSourceVisitor(buffer); astNode.accept(visitor); - return dartfmt(buffer.toString()); + var source = buffer.toString(); + try { + return dartfmt(source); + } on FormatterException catch (_) { + return source; + } } -// TODO(matanl): Remove copied-pasted methods when API becomes available. -// https://github.com/dart-lang/sdk/issues/27169 +// https://github.com/dart-lang/code_builder/issues/16 class _PrettyToSourceVisitor extends ToSourceVisitor { - // https://github.com/dart-lang/sdk/issues/27301 final StringBuffer _buffer; _PrettyToSourceVisitor(PrintBuffer buffer) diff --git a/lib/src/scope.dart b/lib/src/scope.dart index f852b0f..f6f6ec4 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -2,81 +2,115 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -part of code_builder; +import 'package:analyzer/analyzer.dart'; -/// Determines an [Identifier] deppending on where it appears. +import 'builders/file.dart'; +import 'tokens.dart'; + +/// Returns an identifier for [name], using [scope] to enforce prefixing. +/// +/// It is _required_ within `code_builder` to use this API instead of creating +/// identifeirs manually in places where an obvious API collision could occur +/// (i.e. imported references). +/// +/// If [scope] is null, no prefixing is applied. +/// +/// ## Example +/// // May output `hello.Hello`. +/// identifier(scope, 'Hello', 'pacakge:hello/hello.dart') +Identifier identifier(Scope scope, String name, [String importFrom]) { + return (scope ?? Scope.identity).identifier(name, importFrom); +} + +/// Maintains an imported reference scope to avoid conflicts in generated code. /// -/// __Example use__: +/// ## Example /// void useContext(Scope scope) { -/// // Prints Identifier "i1.Foo" -/// print(scope.getIdentifier('Foo', 'package:foo/foo.dart'); +/// // May print 'i1.Foo'. +/// print(scope.identifier('Foo', 'package:foo/foo.dart')); /// -/// // Prints Identifier "i1.Bar" -/// print(scope.getIdentifier('Bar', 'package:foo/foo.dart'); +/// // May print 'i1.Bar'. +/// print(scope.identifier('Bar', 'package:foo/foo.dart')); /// -/// // Prints Identifier "i2.Baz" -/// print(scope.getIdentifier('Baz', 'package:bar/bar.dart'); +/// // May print 'i2.Bar'. +/// print(scope.getIdentifier('Baz', 'package:bar/bar.dart')); /// } abstract class Scope { - /// Create a default scope context. + /// A no-op [Scope]. Ideal for use for tests or example cases. /// - /// Actual implementation is _not_ guaranteed, only that all import prefixes - /// will be unique in a given scope (actual implementation may be naive). - factory Scope() = _IncrementingScope; + /// **WARNING**: Does not collect import statements. This is only really + /// advisable for use in tests but not production code. To use import + /// collection but not prefixing, see [Scope.dedupe]. + static const Scope identity = const _IdentityScope(); - /// Create a context that just de-duplicates imports (no scoping). - factory Scope.dedupe() = _DeduplicatingScope; + /// Create a new scoping context. + /// + /// Actual implementation of [Scope] is _not_ guaranteed, only that all + /// import prefixes will be unique in a given scope. + factory Scope() = _IncrementingScope; - /// Create a context that does nothing. - const factory Scope.identity() = _IdentityScope; + /// Create a context that just collects and de-duplicates imports. + /// + /// Prefixing is _not_ applied. + factory Scope.dedupe() => new _DeduplicatingScope(); - /// Given a [symbol] and its known [importUri], return an [Identifier]. - Identifier getIdentifier(String symbol, String importUri); + /// Given a [name] and and import path, returns an [Identifier]. + Identifier identifier(String name, String importFrom); - /// Returns a list of all imports needed to resolve identifiers. - Iterable getImports(); + /// Return a list of import statements. + List toImports(); } -class _DeduplicatingScope implements Scope { +class _DeduplicatingScope extends _IdentityScope { final Set _imports = new Set(); @override - Identifier getIdentifier(String symbol, String import) { - _imports.add(import); - return _stringIdentifier(symbol); + Identifier identifier(String name, [String importFrom]) { + if (importFrom != null) { + _imports.add(importFrom); + } + return super.identifier(name); } @override - Iterable getImports() { - return _imports.map/* new ImportBuilder(i)); - } + List toImports() => + _imports.map((uri) => new ImportBuilder(uri)).toList(); } class _IdentityScope implements Scope { const _IdentityScope(); @override - Identifier getIdentifier(String symbol, _) => _stringIdentifier(symbol); + Identifier identifier(String name, [_]) { + return new SimpleIdentifier(stringToken(name)); + } @override - Iterable getImports() => const []; + List toImports() => const []; } -class _IncrementingScope implements Scope { +class _IncrementingScope extends _IdentityScope { final Map _imports = {}; - int _counter = 0; + int _counter = 1; @override - Identifier getIdentifier(String symbol, String import) { - var newId = _imports.putIfAbsent(import, () => ++_counter); + Identifier identifier(String name, [String importFrom]) { + if (importFrom == null) { + return super.identifier(name); + } + var newId = _imports.putIfAbsent(importFrom, () => _counter++); return new PrefixedIdentifier( - _stringIdentifier('_i$newId'), $period, _stringIdentifier(symbol)); + super.identifier('_i$newId'), + $period, + super.identifier(name), + ); } @override - Iterable getImports() { - return _imports.keys.map/* new ImportBuilder(i, prefix: '_i${_imports[i]}')); + List toImports() { + return _imports.keys.map((uri) { + return new ImportBuilder(uri, prefix: '_i${_imports[uri]}'); + }).toList(); } } diff --git a/lib/src/tokens.dart b/lib/src/tokens.dart index 2ca6c83..17e70b5 100644 --- a/lib/src/tokens.dart +++ b/lib/src/tokens.dart @@ -5,85 +5,139 @@ import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart'; -// Keywords /// The `abstract` token. final Token $abstract = new KeywordToken(Keyword.ABSTRACT, 0); -/// The `extends` token. -final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); +/// The `as` token. +final Token $as = new KeywordToken(Keyword.AS, 0); -/// The `implements` token. -final Token $implements = new KeywordToken(Keyword.IMPLEMENTS, 0); +/// The `assert` token. +final Token $assert = new KeywordToken(Keyword.ASSERT, 0); -/// The `with` token. -final Token $with = new KeywordToken(Keyword.WITH, 0); +/// The `@` token. +final Token $at = new Token(TokenType.AT, 0); -/// The `static` token. -final Token $static = new KeywordToken(Keyword.STATIC, 0); +/// The `class` token. +final Token $class = new KeywordToken(Keyword.CLASS, 0); -/// The `final` token. -final Token $final = new KeywordToken(Keyword.FINAL, 0); +/// The `]` token. +final Token $closeBracket = new Token(TokenType.CLOSE_SQUARE_BRACKET, 0); + +/// The '}' token. +final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); + +/// The ')' token. +final Token $closeParen = new Token(TokenType.CLOSE_PAREN, 0); + +/// The ':' token. +final Token $colon = new Token(TokenType.COLON, 0); /// The `const` token. final Token $const = new KeywordToken(Keyword.CONST, 0); -/// The `var` token. -final Token $var = new KeywordToken(Keyword.VAR, 0); +/// The `/` token. +final Token $divide = new Token(TokenType.SLASH, 0); -/// The `this` token. -final Token $this = new KeywordToken(Keyword.THIS, 0); +/// The `else` token. +final Token $else = new KeywordToken(Keyword.ELSE, 0); + +/// The '=' token. +final Token $equals = new Token(TokenType.EQ, 0); + +/// The `==` token. +final Token $equalsEquals = new Token(TokenType.EQ_EQ, 0); + +/// The `extends` token. +final Token $extends = new KeywordToken(Keyword.EXTENDS, 0); + +/// The `false` token. +final Token $false = new KeywordToken(Keyword.FALSE, 0); + +/// The `final` token. +final Token $final = new KeywordToken(Keyword.FINAL, 0); + +/// The `>` token. +final Token $gt = new Token(TokenType.GT, 0); + +/// The `if` token. +final Token $if = new KeywordToken(Keyword.IF, 0); + +// Simple tokens + +/// The `implements` token. +final Token $implements = new KeywordToken(Keyword.IMPLEMENTS, 0); /// The `library` token. final Token $library = new KeywordToken(Keyword.LIBRARY, 0); -/// The `part` token. -final Token $part = new KeywordToken(Keyword.PART, 0); +/// The `<` token. +final Token $lt = new Token(TokenType.LT, 0); -/// The `of` token. -final Token $of = new StringToken(TokenType.KEYWORD, 'of', 0); +/// The `-` token. +final Token $minus = new Token(TokenType.MINUS, 0); -/// The `true` token. -final Token $true = new KeywordToken(Keyword.TRUE, 0); +/// The `*` token. +final Token $multiply = new Token(TokenType.STAR, 0); -/// The `false` token. -final Token $false = new KeywordToken(Keyword.FALSE, 0); +/// The `new` token. +final Token $new = new KeywordToken(Keyword.NEW, 0); + +/// The `!` token. +final Token $not = new Token(TokenType.BANG, 0); + +/// The `!=` token. +final Token $notEquals = new Token(TokenType.BANG_EQ, 0); /// The `null` token. final Token $null = new KeywordToken(Keyword.NULL, 0); -/// The `new` token. -final Token $new = new KeywordToken(Keyword.NEW, 0); +/// The '??=' token. +final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); -// Simple tokens -/// The '(' token. -final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); +/// The `of` token. +final Token $of = new StringToken(TokenType.KEYWORD, 'of', 0); -/// The ')' token. -final Token $closeParen = new Token(TokenType.CLOSE_PAREN, 0); +/// The '[` token. +final Token $openBracket = new Token(TokenType.OPEN_SQUARE_BRACKET, 0); /// The '{' token. final Token $openCurly = new Token(TokenType.OPEN_CURLY_BRACKET, 0); -/// The '}' token. -final Token $closeCurly = new Token(TokenType.CLOSE_CURLY_BRACKET, 0); +/// The '(' token. +final Token $openParen = new Token(TokenType.OPEN_PAREN, 0); -/// The ':' token. -final Token $colon = new Token(TokenType.COLON, 0); +/// The `part` token. +final Token $part = new KeywordToken(Keyword.PART, 0); + +/// The '.' token. +final Token $period = new Token(TokenType.PERIOD, 0); + +/// The `+` token. +final Token $plus = new Token(TokenType.PLUS, 0); + +/// The `return` token. +final Token $return = new KeywordToken(Keyword.RETURN, 0); /// The ';' token. final Token $semicolon = new Token(TokenType.SEMICOLON, 0); -/// The '=' token. -final Token $equals = new Token(TokenType.EQ, 0); +/// The `static` token. +final Token $static = new KeywordToken(Keyword.STATIC, 0); -/// The '??=' token. -final Token $nullAwareEquals = new Token(TokenType.QUESTION_QUESTION_EQ, 0); +/// The `this` token. +final Token $this = new KeywordToken(Keyword.THIS, 0); -/// The '.' token. -final Token $period = new Token(TokenType.PERIOD, 0); +/// The `true` token. +final Token $true = new KeywordToken(Keyword.TRUE, 0); -/// Returns a string token for the given string [s]. -StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); +/// The `var` token. +final Token $var = new KeywordToken(Keyword.VAR, 0); + +/// The `with` token. +final Token $with = new KeywordToken(Keyword.WITH, 0); /// Returns an int token for the given int [value]. StringToken intToken(int value) => new StringToken(TokenType.INT, '$value', 0); + +/// Returns a string token for the given string [s]. +StringToken stringToken(String s) => new StringToken(TokenType.STRING, s, 0); diff --git a/lib/testing.dart b/lib/testing.dart new file mode 100644 index 0000000..ef6ae98 --- /dev/null +++ b/lib/testing.dart @@ -0,0 +1,100 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/pretty_printer.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:matcher/matcher.dart'; + +/// Returns a [Matcher] that checks an [AstBuilder] versus [source]. +/// +/// On failure, uses the default string matcher to show a detailed diff between +/// the expected and actual source code results. +/// +/// If [pretty] is set, uses another `toSource` method that adds additional +/// line breaks to make the output more readable and idiomatic. +Matcher equalsSource( + String source, { + bool pretty: false, + Scope scope: Scope.identity, +}) { + var canParse = false; + try { + source = dartfmt(source); + canParse = true; + } on FormatterException catch (_) {} + return new _EqualsSource( + scope, + source, + canParse, + pretty, + ); +} + +class _EqualsSource extends Matcher { + final Scope _scope; + final String _source; + final bool _canParse; + final bool _pretty; + + _EqualsSource(this._scope, this._source, this._canParse, this._pretty); + + @override + Description describe(Description description) { + if (_canParse) { + return equals(_source).describe(description); + } else { + return equalsIgnoringWhitespace(_source).describe(description); + } + } + + @override + Description describeMismatch( + item, + Description mismatchDescription, + Map matchState, + bool verbose, + ) { + if (item is AstBuilder) { + var origin = _formatAst(item); + if (_canParse) { + return equals(_source).describeMismatch( + origin, + mismatchDescription.addDescriptionOf(origin), + matchState, + verbose, + ); + } else { + return equalsIgnoringWhitespace(_source).describeMismatch( + origin, + mismatchDescription.addDescriptionOf(origin), + matchState, + verbose, + ); + } + } else { + return mismatchDescription.add('$item is not a CodeBuilder'); + } + } + + @override + bool matches(item, _) { + if (item is AstBuilder) { + if (_canParse) { + return equals(_formatAst(item)).matches(_source, {}); + } else { + return equalsIgnoringWhitespace(_formatAst(item)).matches(_source, {}); + } + } + return false; + } + + String _formatAst(AstBuilder builder) { + var astNode = builder.buildAst(_scope); + if (_canParse) { + return dartfmt(_pretty ? prettyToSource(astNode) : astNode.toSource()); + } + return astNode.toSource(); + } +} diff --git a/lib/testing/equals_source.dart b/lib/testing/equals_source.dart deleted file mode 100644 index e79036c..0000000 --- a/lib/testing/equals_source.dart +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/src/tokens.dart'; -import 'package:matcher/matcher.dart'; - -/// Returns identifiers that are just the file name. -/// -/// For example `getIdentifier('Foo', 'package:foo/foo.dart')` would return -/// the identifier "foo.Foo". This isn't safe enough for use in a actual -/// code-gen but makes it easy to debug issues in our tests. -const Scope simpleNameScope = const _SimpleNameScope(); - -/// Returns a [Matcher] that checks a [CodeBuilder] versus [source]. -/// -/// On failure, uses the default string matcher to show a detailed diff between -/// the expected and actual source code results. -/// -/// **NOTE**: By default it both runs `dartfmt` _and_ prints the source with -/// additional formatting over the default `Ast.toSource` implementation (i.e. -/// adds new lines between methods in classes, and more). -/// -/// If you have code that is not consider a valid compilation unit (like an -/// expression, you should flip [format] to `false`). -Matcher equalsSource( - String source, { - bool format: true, - Scope scope: const Scope.identity(), -}) { - return new _EqualsSource( - scope, - format ? dartfmt(source) : source, - format, - ); -} - -Identifier _stringId(String s) => new SimpleIdentifier(stringToken(s)); - -class _EqualsSource extends Matcher { - final Scope _scope; - final String _source; - final bool _isFormatted; - - _EqualsSource(this._scope, this._source, this._isFormatted); - - @override - Description describe(Description description) { - return equals(_source).describe(description); - } - - @override - Description describeMismatch( - item, - Description mismatchDescription, - Map matchState, - bool verbose, - ) { - if (item is CodeBuilder) { - var origin = _formatAst(item); - print(origin); - return equals(_source).describeMismatch( - origin, - mismatchDescription.addDescriptionOf(origin), - matchState, - verbose, - ); - } else { - return mismatchDescription.add('$item is not a CodeBuilder'); - } - } - - @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 - Identifier getIdentifier(String symbol, String importUri) { - var fileWithoutExt = - Uri.parse(importUri).pathSegments.last.split('.').first; - return new PrefixedIdentifier( - _stringId(fileWithoutExt), - $period, - _stringId(symbol), - ); - } - - @override - Iterable getImports() => const []; -} diff --git a/pubspec.yaml b/pubspec.yaml index 483ee3a..84f1bd9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 0.1.3 +version: 1.0.0-alpha 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 deleted file mode 100644 index e20cb54..0000000 --- a/test/builders/class_builder_test.dart +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit an empty class', () { - expect(new ClassBuilder('Animal'), equalsSource('class Animal {}')); - }); - - test('should emit an abstract class', () { - expect( - new ClassBuilder.asAbstract('Animal'), - equalsSource('abstract class Animal {}'), - ); - }); - - test('should emit a class extending another class', () { - expect( - new ClassBuilder('Animal', extend: new TypeBuilder('Organism')), - equalsSource('class Animal extends Organism {}'), - ); - }); - - test('should emit a class implementing another class', () { - expect( - new ClassBuilder('Animal', implement: [new TypeBuilder('Delicious')]), - equalsSource('class Animal implements Delicious {}'), - ); - }); - - test('should emit a class extending and mixing in another class', () { - expect( - new ClassBuilder( - 'Animal', - extend: new TypeBuilder('Organism'), - mixin: [new TypeBuilder('Breathing')], - ), - equalsSource('class Animal extends Organism with Breathing {}'), - ); - }); - - test('should emit a class with a method', () { - expect( - new ClassBuilder('Animal') - ..addMethod(new MethodBuilder( - name: 'toString', - returns: typeString, - )..setExpression(const LiteralString('Delicious'))), - equalsSource(r''' - class Animal { - String toString() => 'Delicious'; - } - '''), - ); - }); - - test('should emit an abstract class with an abstract method', () { - expect( - new ClassBuilder.asAbstract('Animal') - ..addMethod( - new MethodBuilder.returnVoid(name: 'eat', abstract: true), - ), - equalsSource(r''' - abstract class Animal { - void eat(); - } - '''), - ); - }); - - test('should emit a class with a static method', () { - expect( - new ClassBuilder('Animal') - ..addMethod( - new MethodBuilder( - name: 'create', - static: true, - returns: new TypeBuilder('Animal'), - )..setExpression(literalNull), - ), - equalsSource(r''' - class Animal { - static Animal create() => null; - } - '''), - ); - }); - - test('should emit a class with a metadata annotation', () { - expect( - new ClassBuilder('Animal')..addAnnotation(atDeprecated()), - equalsSource(r''' - @deprecated - class Animal {} - '''), - ); - }); - - test('should emit a class with an invoking metadata annotation', () { - expect( - new ClassBuilder('Animal') - ..addAnnotation(atDeprecated('We ate them all')), - equalsSource(r''' - @Deprecated('We ate them all') - class Animal {} - '''), - ); - }); - - group('constructors', () { - ClassBuilder clazz; - - setUp(() { - clazz = new ClassBuilder('Animal'); - }); - - test('default constructor', () { - clazz.addConstructor(new ConstructorBuilder()); - expect( - clazz, - equalsSource( - r''' - class Animal { - Animal(); - } - ''', - )); - }); - - test('default const constructor', () { - clazz.addConstructor(new ConstructorBuilder.isConst()); - expect( - clazz, - equalsSource( - r''' - class Animal { - const Animal(); - } - ''', - )); - }); - - test('initializing fields', () { - clazz.addConstructor(new ConstructorBuilder() - ..addParameter( - new ParameterBuilder('a', field: true), - ) - ..addParameter( - new ParameterBuilder.optional('b', field: true), - )); - expect( - clazz, - equalsSource( - r''' - class Animal { - Animal(this.a, [this.b]); - } - ''', - )); - }); - }); -} diff --git a/test/builders/class_test.dart b/test/builders/class_test.dart new file mode 100644 index 0000000..40fe307 --- /dev/null +++ b/test/builders/class_test.dart @@ -0,0 +1,163 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/src/builders/method.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit an empty class', () { + expect( + clazz('Animal'), + equalsSource(r''' + class Animal {} + '''), + ); + }); + + test('should emit a class with an annotation', () { + expect( + clazz('Animal', [reference('deprecated')]), + equalsSource(r''' + @deprecated + class Animal {} + '''), + ); + }); + + test('should emit a class that extends another', () { + expect( + clazz('Animal', [extend(reference('Life'))]), + equalsSource(r''' + class Animal extends Life {} + '''), + ); + }); + + test('should emit a class that implements another', () { + expect( + clazz('Animal', [implement(reference('Living'))]), + equalsSource(r''' + class Animal implements Living {} + '''), + ); + }); + + test('should emit a class that mixes in another', () { + expect( + clazz('Animal', [mixin(reference('Living'))]), + equalsSource(r''' + class Animal extends Object with Living {} + '''), + ); + }); + + test('should emit a class with a constructor', () { + expect( + clazz('Animal', [ + constructor(), + ]), + equalsSource(r''' + class Animal { + Animal(); + } + '''), + ); + }); + + test('should emit a class with a named constructor', () { + expect( + clazz('Animal', [ + constructorNamed('internal'), + ]), + equalsSource(r''' + class Animal { + Animal.internal(); + } + '''), + ); + }); + + test('should emit a class with a constructor with parameters', () { + expect( + clazz('Animal', [ + constructor([ + parameter('name', [lib$core.String]), + thisField( + named( + parameter('age').asOptional(literal(0)), + ), + ) + ]) + ]), + equalsSource(r''' + class Animal { + Animal(String name, {this.age: 0}); + } + '''), + ); + }); + + test('should emit a class with fields', () { + expect( + clazz('Animal', [ + asStatic( + varField('static1', type: lib$core.String, value: literal('Hello')), + ), + asStatic( + varFinal('static2', type: lib$core.List, value: literal([])), + ), + asStatic( + varConst('static3', type: lib$core.bool, value: literal(true)), + ), + varField('var1', type: lib$core.String, value: literal('Hello')), + varFinal('var2', type: lib$core.List, value: literal([])), + varConst('var3', type: lib$core.bool, value: literal(true)), + ]), + equalsSource(r''' + class Animal { + static String static1 = 'Hello'; + static final List static2 = []; + static const bool static3 = true; + String var1 = 'Hello'; + final List var2 = []; + const bool var3 = true; + } + '''), + ); + }); + + test('should emit a class with methods', () { + expect( + clazz('Animal', [ + asStatic(method('staticMethod', [ + lib$core.$void, + lib$core.print.call([literal('Called staticMethod')]), + ])), + method('instanceMethod', [ + lib$core.$void, + lib$core.print.call([literal('Called instanceMethod')]), + ]), + constructor([ + lib$core.print.call([literal('Called constructor')]), + ]), + ]), + equalsSource(r''' + class Animal { + Animal() { + print('Called constructor'); + } + static void staticMethod() { + print('Called staticMethod'); + } + void instanceMethod() { + print('Called instanceMethod'); + } + } + '''), + ); + }); +} diff --git a/test/builders/expression_test.dart b/test/builders/expression_test.dart new file mode 100644 index 0000000..5d6b453 --- /dev/null +++ b/test/builders/expression_test.dart @@ -0,0 +1,247 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group('literal', () { + test('should emit a null', () { + expect(literal(null), equalsSource(r'null')); + }); + + test('should emit true', () { + expect(literal(true), equalsSource(r'true')); + }); + + test('should emit false', () { + expect(literal(false), equalsSource(r'false')); + }); + + test('should emit an int', () { + expect(literal(5), equalsSource(r'5')); + }); + + test('should emit a double', () { + expect(literal(5.5), equalsSource(r'5.5')); + }); + + test('should emit a string', () { + expect(literal('Hello'), equalsSource(r"'Hello'")); + }); + + test('should emit a list', () { + expect(literal([1, 2, 3]), equalsSource(r'[1, 2, 3]')); + }); + + test('should emit a map', () { + expect(literal({1: 2, 2: 3}), equalsSource(r'{1 : 2, 2 : 3}')); + }); + }); + + test('should emit an assert statemnet', () { + expect( + literal(true).asAssert(), + equalsSource(r''' + assert(true); + '''), + ); + }); + + test('should emit an assign expression', () { + expect( + literal(true).asAssign('flag'), + equalsSource(r''' + flag = true + '''), + ); + }); + + test('should emit an assign expression with a null aware', () { + expect( + literal(true).asAssign('flag', nullAware: true), + equalsSource(r''' + flag ??= true + '''), + ); + }); + + test('should emit a const variable assignment statement', () { + expect( + literal(true).asConst('flag'), + equalsSource(r''' + const flag = true; + '''), + ); + }); + + test('should emit a final variable assignment statement', () { + expect( + literal(true).asFinal('flag'), + equalsSource(r''' + final flag = true; + '''), + ); + }); + + test('should emit a simple var assignment statement', () { + expect( + literal(true).asVar('flag'), + equalsSource(r''' + var flag = true; + '''), + ); + }); + + test('should emit a typed assignemnt statement', () { + expect( + literal(true).asVar('flag', lib$core.bool), + equalsSource(r''' + bool flag = true; + '''), + ); + }); + + test('should emit a return statement', () { + expect( + literal(true).asReturn(), + equalsSource(r''' + return true; + '''), + ); + }); + + test('should emit an expression as a statement', () { + expect( + literal(true).asStatement(), + equalsSource(r''' + true; + '''), + ); + }); + + test('should call an expression as a function', () { + expect( + lib$core.identical.call([literal(true), literal(true)]), + equalsSource(r''' + identical(true, true) + '''), + ); + }); + + test('should call an expression with named arguments', () { + expect( + reference('doThing').call( + [literal(true)], + { + 'otherFlag': literal(false), + }, + ), + equalsSource(r''' + doThing(true, otherFlag: false) + '''), + ); + }); + + test('should call a method on an expression', () { + expect( + explicitThis.invoke('doThing', [literal(true)]), + equalsSource(r''' + this.doThing(true) + '''), + ); + }); + + test('should emit an identical() expression', () { + expect( + literal(true).identical(literal(false)), + equalsSource(r''' + identical(true, false) + '''), + ); + }); + + test('should emit an equality (==) expression', () { + expect( + literal(true).equals(literal(false)), + equalsSource(r''' + true == false + '''), + ); + }); + + test('should emit a not equals (!=) expression', () { + expect( + literal(true).notEquals(literal(false)), + equalsSource(r''' + true != false + '''), + ); + }); + + test('should emit a negated expression', () { + expect( + reference('foo').negate(), + equalsSource(r''' + !(foo) + '''), + ); + }); + + test('should add two expressions', () { + expect( + literal(1) + literal(2), + equalsSource(r''' + 1 + 2 + '''), + ); + }); + + test('should subtract two expressions', () { + expect( + literal(2) - literal(1), + equalsSource(r''' + 2 - 1 + '''), + ); + }); + + test('should multiply two expressions', () { + expect( + literal(2) * literal(3), + equalsSource(r''' + 2 * 3 + '''), + ); + }); + + test('should divide two expressions', () { + expect( + literal(3) / literal(2), + equalsSource(r''' + 3 / 2 + '''), + ); + }); + + test('should wrap an expressions in ()', () { + expect( + literal(true).parentheses(), + equalsSource(r''' + (true) + '''), + ); + }); + + test('should return as a negative expression', () { + expect( + literal(1).negative(), + equalsSource(r''' + -(1) + '''), + ); + }); +} diff --git a/test/builders/field_builder_test.dart b/test/builders/field_builder_test.dart deleted file mode 100644 index 95954b4..0000000 --- a/test/builders/field_builder_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit an unitialized simple variable', () { - expect( - new FieldBuilder('name'), - equalsSource(r'var name;'), - ); - }); - - test('should emit a typed variable', () { - expect( - new FieldBuilder('name', type: typeString), - equalsSource(r'String name;'), - ); - }); - - test('should emit an initialized variable', () { - expect( - new FieldBuilder( - 'name', - type: typeString, - initialize: const LiteralString('Jill User'), - ), - equalsSource(r"String name = 'Jill User';"), - ); - }); - - test('should emit a final variable', () { - expect( - new FieldBuilder.isFinal('name'), - equalsSource(r'final name;'), - ); - }); - - test('should emit a const variable', () { - expect( - new FieldBuilder.isConst('name'), - equalsSource(r'const name;'), - ); - }); -} diff --git a/test/builders/field_test.dart b/test/builders/field_test.dart new file mode 100644 index 0000000..fcadb66 --- /dev/null +++ b/test/builders/field_test.dart @@ -0,0 +1,33 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('emit a var', () { + expect( + varField('a', type: lib$core.String, value: literal('Hello')), + equalsSource(r''' + String a = 'Hello'; + '''), + ); + }); + + test('emit a final', () { + expect( + varFinal('a', type: lib$core.String, value: literal('Hello')), + equalsSource(r''' + final String a = 'Hello'; + '''), + ); + }); + + test('emit a const', () { + expect( + varConst('a', type: lib$core.String, value: literal('Hello')), + equalsSource(r''' + const String a = 'Hello'; + '''), + ); + }); +} diff --git a/test/builders/file_builder_test.dart b/test/builders/file_builder_test.dart deleted file mode 100644 index 8329b97..0000000 --- a/test/builders/file_builder_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit a blank file', () { - expect(new LibraryBuilder(), equalsSource('')); - }); - - test('should emit a file with a library directive', () { - expect(new LibraryBuilder('code_builder'), - equalsSource('library code_builder;')); - }); - - test('should emit a file with a part of directive', () { - expect( - new PartBuilder('code_builder'), - equalsSource('part of code_builder;'), - ); - }); - - test('should emit an import directive', () { - expect( - new ImportBuilder('package:foo/foo.dart'), - equalsSource("import 'package:foo/foo.dart';"), - ); - }); - - test('should emit an import directive and a prefix', () { - expect( - new ImportBuilder('package:foo/foo.dart', prefix: 'foo'), - equalsSource("import 'package:foo/foo.dart' as foo;"), - ); - }); - - test('should emit an export directive', () { - expect( - new ExportBuilder('package:foo/foo.dart'), - equalsSource("export 'package:foo/foo.dart';"), - ); - }); -} diff --git a/test/builders/method_builder_test.dart b/test/builders/method_builder_test.dart deleted file mode 100644 index 3b0c70e..0000000 --- a/test/builders/method_builder_test.dart +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -void main() { - test('should emit a simple "void" method', () { - expect( - new MethodBuilder.returnVoid(name: 'main'), - equalsSource('void main() {}'), - ); - }); - - test('should emit a method returning a String', () { - expect( - new MethodBuilder(name: 'toString', returns: typeString), - equalsSource('String toString() {}'), - ); - }); - - test('should emit a method returning a scoped Type', () { - expect( - new MethodBuilder( - name: 'toFoo', - returns: new TypeBuilder('Foo', importFrom: 'package:foo/foo.dart'), - ) - ..addParameter(new ParameterBuilder( - 'context', - type: - new TypeBuilder('Context', importFrom: 'package:bar/bar.dart'), - )), - equalsSource( - r''' - foo.Foo toFoo(bar.Context context) {} - ''', - scope: simpleNameScope, - )); - }); - - test('should emit a method returning a scoped Type and scoped Param', () { - expect( - new MethodBuilder( - name: 'toFoo', - returns: new TypeBuilder('Foo', importFrom: 'package:foo/foo.dart'), - ), - equalsSource( - r''' - foo.Foo toFoo() {} - ''', - scope: simpleNameScope, - )); - }); - - test('should emit a method annotated', () { - expect( - new MethodBuilder(name: 'oldMethod')..addAnnotation(atDeprecated()), - equalsSource( - r''' - @deprecated - oldMethod() {} - ''', - )); - }); - - group('with an expression', () { - test('should return true', () { - expect( - new MethodBuilder(name: 'isSupported', returns: typeBool) - ..setExpression(literalTrue), - equalsSource( - r''' - bool isSupported() => true; - ''', - ), - ); - }); - - test('should return false', () { - expect( - new MethodBuilder(name: 'isSupported', returns: typeBool) - ..setExpression(literalFalse), - equalsSource( - r''' - bool isSupported() => false; - ''', - ), - ); - }); - - test('should return null', () { - expect( - new MethodBuilder(name: 'isSupported')..setExpression(literalNull), - equalsSource( - r''' - isSupported() => null; - ''', - ), - ); - }); - - test('should return "Hello World"', () { - expect( - new MethodBuilder(name: 'sayHello', returns: typeString) - ..setExpression( - const LiteralString('Hello World'), - ), - equalsSource( - r''' - String sayHello() => 'Hello World'; - ''', - ), - ); - }); - - group('with statements', () { - test('should work with an expression', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement(const LiteralString('Hello World').toStatement()), - equalsSource(r''' - void main() { - 'Hello World'; - } - '''), - ); - }); - - test('should work with an assignment', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement(new ExpressionBuilder.assignment( - 'foo', - const LiteralString('Hello World'), - ) - .toStatement()), - equalsSource(r''' - void main() { - foo = 'Hello World'; - } - '''), - ); - }); - - test('should work with a null-aware assignment', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement(new ExpressionBuilder.assignment( - 'foo', - const LiteralString('Hello World'), - nullAware: true, - ) - .toStatement()), - equalsSource(r''' - void main() { - foo ??= 'Hello World'; - } - '''), - ); - }); - - test('should work invoking an expression', () { - expect( - new MethodBuilder.returnVoid(name: 'main') - ..addStatement( - new ExpressionBuilder.invoke('print', positional: [ - const LiteralString('Hello World').invokeSelf( - 'substring', - positional: [const LiteralInt(1)], - ) - ]).toStatement(), - ), - equalsSource(r''' - void main() { - print('Hello World'.substring(1)); - } - '''), - ); - }); - }); - - group('with parameters:', () { - MethodBuilder method; - - setUp(() { - method = new MethodBuilder.returnVoid(name: 'main'); - }); - - test('single required parameter', () { - method.addParameter(new ParameterBuilder('name', type: typeString)); - expect( - method, - equalsSource( - r''' - void main(String name) {} - ''', - ), - ); - }); - - test('two required parameters', () { - method - ..addParameter(new ParameterBuilder('a', type: typeInt)) - ..addParameter(new ParameterBuilder('b', type: typeInt)); - expect( - method, - equalsSource( - r''' - void main(int a, int b) {} - ''', - ), - ); - }); - - test('one optional parameter', () { - method.addParameter(new ParameterBuilder.optional( - 'name', - type: typeString, - )); - expect( - method, - equalsSource( - r''' - void main([String name]) {} - ''', - ), - ); - }); - - test('one optional parameter with a default value', () { - method.addParameter(new ParameterBuilder.optional( - 'enabled', - type: typeBool, - defaultTo: literalTrue, - )); - expect( - method, - equalsSource( - r''' - void main([bool enabled = true]) {} - ''', - ), - ); - }); - - test('one optional named parameter with a default value', () { - method.addParameter(new ParameterBuilder.named( - 'enabled', - type: typeBool, - defaultTo: literalTrue, - )); - expect( - method, - equalsSource( - r''' - void main({bool enabled: true}) {} - ''', - ), - ); - }); - - test('one optional named parameter with an annotation', () { - method.addParameter(new ParameterBuilder.named( - 'enabled', - type: typeBool, - defaultTo: literalTrue, - )..addAnnotation(atDeprecated())); - expect( - method, - equalsSource( - r''' - void main({@deprecated bool enabled: true}) {} - ''', - ), - ); - }); - }); - - group('invoking method', () { - MethodBuilder method; - - setUp(() { - method = new MethodBuilder(name: 'forward', returns: typeInt); - }); - - test('should call another method with one argument', () { - method.setExpression(new ExpressionBuilder.invoke( - 'forwardImpl', - positional: [const LiteralInt(666)], - )); - expect( - method, - equalsSource(r''' - int forward() => forwardImpl(666); - '''), - ); - }); - - test('should call another method with two arguments', () { - method.setExpression(new ExpressionBuilder.invoke( - 'sum', - positional: [const LiteralInt(1), const LiteralInt(2)], - )); - expect( - method, - equalsSource(r''' - int forward() => sum(1, 2); - '''), - ); - }); - - test('should call another method with a named arguments', () { - method.setExpression(new ExpressionBuilder.invoke( - 'forwardImpl', - named: {'value': const LiteralInt(666)}, - )); - expect( - method, - equalsSource(r''' - int forward() => forwardImpl(value: 666); - '''), - ); - }); - - test('should call another method with many arguments', () { - method.setExpression(new ExpressionBuilder.invoke( - 'forwardImpl', - positional: const [const LiteralInt(3), const LiteralInt(4)], - named: {'a': const LiteralInt(1), 'b': const LiteralInt(2)}, - )); - expect( - method, - equalsSource(r''' - int forward() => forwardImpl(3, 4, a: 1, b: 2); - '''), - ); - }); - }); - }); -} diff --git a/test/builders/method_test.dart b/test/builders/method_test.dart new file mode 100644 index 0000000..e532e62 --- /dev/null +++ b/test/builders/method_test.dart @@ -0,0 +1,172 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a top-level main() function', () { + expect( + method('main'), + equalsSource(r''' + main(); + '''), + ); + }); + + test('should emit a top-level void main() function', () { + expect( + method('main', [ + lib$core.$void, + ]).buildMethod(false).toSource(), + equalsIgnoringWhitespace(r''' + void main(); + '''), + ); + }); + + test('should emit a function with a parameter', () { + expect( + method('main', [ + parameter('args', [lib$core.List]), + ]).buildMethod(false).toSource(), + equalsIgnoringWhitespace(r''' + main(List args); + '''), + ); + }); + + test('should emit a function with multiple parameters', () { + expect( + method('main', [ + parameter('a'), + parameter('b'), + parameter('c').asOptional(), + ]), + equalsSource(r''' + main(a, b, [c]); + '''), + ); + }); + + test('should emit a function with multiple parameters', () { + expect( + method('main', [ + parameter('a'), + parameter('b'), + parameter('c').asOptional(literal(true)), + ]), + equalsSource(r''' + main(a, b, [c = true]); + '''), + ); + }); + + test('should emit a function with named parameters', () { + expect( + method('main', [ + named(parameter('a')), + named(parameter('b').asOptional(literal(true))), + ]).buildMethod(false).toSource(), + equalsIgnoringWhitespace(r''' + main({a, b : true}); + '''), + ); + }); + + group('constructors', () { + test('should emit a simple constructor', () { + expect( + new ConstructorBuilder() + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + Animal(); + '''), + ); + }); + + test('should emit a simple constructor with parameters', () { + expect( + (new ConstructorBuilder()..addPositional(parameter('name'))) + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + Animal(name); + '''), + ); + }); + + test('should emit a simple constructor with field-formal parameters', () { + expect( + (new ConstructorBuilder() + ..addPositional( + parameter('name'), + asField: true, + )) + .buildConstructor( + reference('Animal'), + ) + .toSource(), + equalsIgnoringWhitespace(r''' + Animal(this.name); + '''), + ); + }); + + test('should a method with a lambda value', () { + expect( + lambda('supported', literal(true), returnType: lib$core.bool), + equalsSource(r''' + bool supported() => true; + '''), + ); + }); + }); + + test('should emit a getter with a lambda value', () { + expect( + getter('unsupported', returns: literal(true)), + equalsSource(r''' + get unsupported => true; + '''), + ); + }); + + test('should emit a getter with statements', () { + expect( + getter( + 'values', + returnType: lib$core.Iterable, + statements: [ + literal([]).asReturn(), + ], + ), + equalsSource(r''' + Iterable get values { + return []; + } + '''), + ); + }); + + test('should emit a setter', () { + expect( + setter('name', parameter('name', [lib$core.String]), [ + (reference('name') + literal('!')).asAssign('_name'), + ]), + equalsSource(r''' + set name(String name) { + _name = name + '!'; + } + '''), + ); + }); +} diff --git a/test/builders/parameter_builder.dart b/test/builders/parameter_builder.dart new file mode 100644 index 0000000..8ac2d81 --- /dev/null +++ b/test/builders/parameter_builder.dart @@ -0,0 +1,50 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + test('should emit a simple parameter', () { + expect( + parameter('foo'), + equalsSource(r''' + foo + '''), + ); + }); + + test('should emit a typed parameter', () { + expect( + parameter('foo', [reference('String')]), + equalsSource(r''' + String foo + '''), + ); + }); + + test('should emit an optional parameter with a default value', () { + expect( + parameter('foo').asOptional(literal(true)), + equalsSource(r''' + foo = true + '''), + ); + }); + + test('should emit a named parameter with a default value', () { + expect( + parameter('foo').asOptional(literal(true)).buildNamed(false).toSource(), + equalsIgnoringCase(r'foo : true'), + ); + }); + + test('should emit a field formal parameter', () { + expect( + parameter('foo').buildPositional(true).toSource(), + equalsIgnoringCase(r'this.foo'), + ); + }); +} diff --git a/test/builders/reference_test.dart b/test/builders/reference_test.dart new file mode 100644 index 0000000..f25ae74 --- /dev/null +++ b/test/builders/reference_test.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:test/test.dart'; + +void main() { + group('reference', () {}); +} diff --git a/test/builders/shared_test.dart b/test/builders/shared_test.dart new file mode 100644 index 0000000..a38a062 --- /dev/null +++ b/test/builders/shared_test.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/src/builders/shared.dart'; +import 'package:code_builder/src/tokens.dart'; +import 'package:test/test.dart'; + +void main() { + test('stringIdentifier should return a string identifier', () { + expect(stringIdentifier('Coffee').toSource(), 'Coffee'); + }); + + test('stringToken should return a string token', () { + expect(stringToken('Coffee').value().toString(), 'Coffee'); + }); +} diff --git a/test/builders/statement_test.dart b/test/builders/statement_test.dart new file mode 100644 index 0000000..b899c85 --- /dev/null +++ b/test/builders/statement_test.dart @@ -0,0 +1,85 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group('if statements', () { + test('should emit a simple if statement', () { + expect( + ifThen(literal(true), [ + lib$core.print.call([literal('Hello World')]), + ]), + equalsSource(r''' + if (true) { + print('Hello World'); + } + '''), + ); + }); + + test('should emit an if then else statement', () { + expect( + ifThen(literal(true), [ + lib$core.print.call([literal('TRUE')]), + elseThen([ + lib$core.print.call([literal('FALSE')]), + ]), + ]), + equalsSource(r''' + if (true) { + print('TRUE'); + } else { + print('FALSE'); + } + '''), + ); + }); + + test('should emit an if else then statement', () { + final a = reference('a'); + expect( + ifThen(a.equals(literal(1)), [ + lib$core.print.call([literal('Was 1')]), + elseIf(ifThen(a.equals(literal(2)), [ + lib$core.print.call([literal('Was 2')]), + ])), + ]), + equalsSource(r''' + if (a == 1) { + print('Was 1'); + } else if (a == 2) { + print('Was 2'); + } + '''), + ); + }); + + test('should emit an if else if else statement', () { + final a = reference('a'); + expect( + ifThen( + a.equals(literal(1)), + [ + lib$core.print.call([literal('Was 1')]), + elseIf(ifThen(a.equals(literal(2)), [ + lib$core.print.call([literal('Was 2')]), + elseThen([ + lib$core.print.call([literal('Was ') + a]), + ]), + ])), + ], + ), + equalsSource(r''' + if (a == 1) { + print('Was 1'); + } else if (a == 2) { + print('Was 2'); + } else { + print('Was ' + a); + } + '''), + ); + }); + }); +} diff --git a/test/builders/type_test.dart b/test/builders/type_test.dart new file mode 100644 index 0000000..b9ac7b8 --- /dev/null +++ b/test/builders/type_test.dart @@ -0,0 +1,71 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group('new instance', () { + test('emits a new List', () { + expect( + lib$core.List.newInstance([]), + equalsSource(r''' + new List() + '''), + ); + }); + + test('emits a new List.from', () { + expect( + lib$core.List.namedNewInstance('from', [ + literal([1, 2, 3]), + ]), + equalsSource(r''' + new List.from([1, 2, 3]) + '''), + ); + }); + + test('emits a new const Point', () { + expect( + reference('Point').constInstance([ + literal(1), + literal(2), + ]), + equalsSource(r''' + const Point(1, 2) + '''), + ); + }); + + test('emits a const constructor as an annotation', () { + expect( + clazz('Animal', [ + lib$core.Deprecated + .constInstance([literal('Animals are out of style')]), + ]), + equalsSource(r''' + @Deprecated('Animals are out of style') + class Animal {} + '''), + ); + }); + + test('emits a named const constructor as an annotation', () { + expect( + clazz('Animal', [ + reference('Component').namedConstInstance( + 'stateful', + [], + { + 'selector': literal('animal'), + }, + ), + ]), + equalsSource(r''' + @Component.stateful(selector: 'animal') + class Animal {} + '''), + ); + }); + }); +} diff --git a/test/e2e_test.dart b/test/e2e_test.dart new file mode 100644 index 0000000..1c88780 --- /dev/null +++ b/test/e2e_test.dart @@ -0,0 +1,108 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/dart/core.dart'; +import 'package:code_builder/testing.dart'; +import 'package:test/test.dart'; + +// Closely mirrors the API you'd need to generate dependency injection :) +void main() { + test('Should emit a complex generated file', () { + // Imports from an existing Dart library. + var appRef = reference('App', 'package:app/app.dart'); + var moduleRef = reference('Module', 'package:app/app.dart'); + var thingRef = reference('Thing', 'package:app/app.dart'); + + var clazz = new ClassBuilder('Injector', asImplements: [appRef]); + clazz + ..addField( + new FieldBuilder.asFinal('_module', type: moduleRef), + ) + ..addConstructor(new ConstructorBuilder() + ..addPositional( + new ParameterBuilder('_module'), + asField: true, + )) + ..addMethod(new MethodBuilder( + 'getThing', + returns: thingRef.newInstance([ + reference('_module').invoke('getDep1', []), + reference('_module').invoke('getDep2', []), + ]), + returnType: thingRef, + )..addAnnotation(lib$core.override)); + var lib = new LibraryBuilder() + ..addDirective( + new ImportBuilder('app.dart'), + ) + ..addMember( + clazz, + ); + expect( + lib, + equalsSource( + r''' + import 'app.dart'; + + class Injector implements App { + final Module _module; + + Injector(this._module); + + @override + Thing getThing() => new Thing(_module.getDep1(), _module.getDep2()); + } + ''', + pretty: true, + ), + ); + }); + + test('Should emit a complex generated file with scoping applied', () { + var appRef = reference('App', 'package:app/app.dart'); + var moduleRef = reference('Module', 'package:app/app.dart'); + var thingRef = reference('Thing', 'package:app/thing.dart'); + var lib = new LibraryBuilder.scope() + ..addMember(new ClassBuilder('Injector', asImplements: [appRef]) + ..addField( + new FieldBuilder.asFinal( + '_module', + type: moduleRef, + ), + ) + ..addConstructor(new ConstructorBuilder() + ..addPositional( + new ParameterBuilder('_module'), + asField: true, + )) + ..addMethod(new MethodBuilder( + 'getThing', + returnType: thingRef, + returns: thingRef.newInstance([ + reference('_module').invoke('getDep1', []), + reference('_module').invoke('getDep2', []), + ]), + )..addAnnotation(lib$core.override))); + expect( + lib, + equalsSource( + r''' + import 'package:app/app.dart' as _i1; + import 'package:app/thing.dart' as _i2; + + class Injector implements _i1.App { + final _i1.Module _module; + + Injector(this._module); + + @override + _i2.Thing getThing() => new _i2.Thing(_module.getDep1(), _module.getDep2()); + } + ''', + pretty: true, + ), + ); + }); +} diff --git a/test/integration_test.dart b/test/integration_test.dart deleted file mode 100644 index f7565a8..0000000 --- a/test/integration_test.dart +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:code_builder/code_builder.dart'; -import 'package:code_builder/dart/core.dart'; -import 'package:code_builder/testing/equals_source.dart'; -import 'package:test/test.dart'; - -// Closely mirrors the API you would need to do dependency injection :) -void main() { - test('Emits a complex generated file', () { - var clazz = - new ClassBuilder(r'Injector', implement: [new TypeBuilder('App')]) - ..addField(new FieldBuilder.isFinal( - '_module', - type: const TypeBuilder('Module'), - )) - ..addConstructor(new ConstructorBuilder() - ..addParameter(new ParameterBuilder('_module', field: true))) - ..addMethod( - new MethodBuilder( - name: 'getThing', - returns: const TypeBuilder('Thing'), - ) - ..addAnnotation(atOverride) - ..setExpression(new ExpressionBuilder.invokeNew( - const TypeBuilder('Thing'), - positional: [ - new ExpressionBuilder.invoke('_module.getDep1'), - new ExpressionBuilder.invoke('_module.getDep2'), - ], - )), - ); - var lib = new LibraryBuilder() - ..addDirective( - new ImportBuilder('app.dart'), - ) - ..addDeclaration(clazz); - expect( - lib, - equalsSource( - r''' - import 'app.dart'; - - class Injector implements App { - final Module _module; - - Injector(this._module); - - @override - Thing getThing() => new Thing(_module.getDep1(), _module.getDep2()); - } - ''', - ), - ); - }); - - test('Emits a complex generated file with scoping applied', () { - var clazz = new ClassBuilder( - r'Injector', - implement: [new TypeBuilder('App', importFrom: 'package:app/app.dart')], - ) - ..addField(new FieldBuilder.isFinal( - '_module', - type: const TypeBuilder( - 'Module', - importFrom: 'package:app/app.dart', - ), - )) - ..addConstructor(new ConstructorBuilder() - ..addParameter(new ParameterBuilder('_module', field: true))) - ..addMethod( - new MethodBuilder( - name: 'getThing', - returns: const TypeBuilder( - 'Thing', - importFrom: 'package:app/thing.dart', - ), - ) - ..addAnnotation(atOverride) - ..setExpression(new ExpressionBuilder.invokeNew( - const TypeBuilder( - 'Thing', - importFrom: 'package:app/thing.dart', - ), - positional: [ - new ExpressionBuilder.invoke('_module.getDep1'), - new ExpressionBuilder.invoke('_module.getDep2'), - ], - )), - ); - var lib = new LibraryBuilder.scope()..addDeclaration(clazz); - expect( - lib, - equalsSource( - r''' - import 'package:app/app.dart' as _i1; - import 'dart:core' as _i2; - import 'package:app/thing.dart' as _i3; - - - class Injector implements _i1.App { - final _i1.Module _module; - - Injector(this._module); - - @_i2.override - _i3.Thing getThing() => new _i3.Thing(_module.getDep1(), _module.getDep2()); - } - ''', - ), - ); - }); - - test('Emits a complex generated file with conflicting imports scoped', () { - var lib = new LibraryBuilder.scope() - ..addDeclaration(new MethodBuilder( - name: 'doThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - )) - ..addDeclaration(new MethodBuilder( - name: 'doOtherThing', - returns: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/alternative.dart', - )) - ..addParameter(new ParameterBuilder( - 'thing', - type: new TypeBuilder( - 'Thing', - importFrom: 'package:thing/thing.dart', - ), - ))); - expect( - lib, - equalsSource( - r''' - import 'package:thing/thing.dart' as _i1; - import 'package:thing/alternative.dart' as _i2; - - _i1.Thing doThing() {} - _i2.Thing doOtherThing(_i1.Thing thing) {} - ''', - ), - ); - }); -} diff --git a/test/scope_test.dart b/test/scope_test.dart index a75532a..64b41d5 100644 --- a/test/scope_test.dart +++ b/test/scope_test.dart @@ -1,96 +1,84 @@ -// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - import 'package:analyzer/analyzer.dart'; -import 'package:code_builder/code_builder.dart'; +import 'package:code_builder/src/scope.dart'; import 'package:test/test.dart'; void main() { - group('Identity scope', () { - Scope scope; - - setUp(() => scope = const Scope.identity()); - - test('should do nothing', () { - var identifiers = [ - scope.getIdentifier('Foo', 'package:foo/foo.dart'), - scope.getIdentifier('Bar', 'package:foo/foo.dart'), - scope.getIdentifier('Baz', 'package:baz/baz.dart'), - ].map/**/((i) => i.toSource()); - - expect( - identifiers, - [ - 'Foo', - 'Bar', - 'Baz', - ], - ); - - expect(scope.getImports(), isEmpty); - }); - }); + Scope scope; - group('Deduplicating scope', () { - Scope scope; + tearDown(() => scope = null); - setUp(() => scope = new Scope.dedupe()); + test('Identity scope should do nothing', () { + scope = Scope.identity; - test('should just output non-prefixed and de-duplicate imports', () { - var identifiers = [ - scope.getIdentifier('Foo', 'package:foo/foo.dart'), - scope.getIdentifier('Bar', 'package:foo/foo.dart'), - scope.getIdentifier('Baz', 'package:baz/baz.dart'), - ].map/**/((i) => i.toSource()); + var identifiers = [ + scope.identifier('Foo', 'package:foo/foo.dart'), + scope.identifier('Bar', 'package:foo/foo.dart'), + scope.identifier('Baz', 'package:baz/baz.dart'), + ].map((i) => i.toSource()); - expect( - identifiers, - [ - 'Foo', - 'Bar', - 'Baz', - ], - ); + expect( + 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.toImports(), isEmpty); }); - group('Incrementing scope', () { - Scope scope; - - setUp(() => scope = new Scope()); - - test('should out prefixed with a counter', () { - var identifiers = [ - scope.getIdentifier('Foo', 'package:foo/foo.dart'), - scope.getIdentifier('Bar', 'package:foo/foo.dart'), - scope.getIdentifier('Baz', 'package:baz/baz.dart'), - ].map/**/((i) => i.toSource()); - - expect( - identifiers, - [ - '_i1.Foo', - '_i1.Bar', - '_i2.Baz', - ], - ); + test('Deduplicating scope should deduplicate imports', () { + scope = new Scope.dedupe(); + + var identifiers = [ + scope.identifier('Foo', 'package:foo/foo.dart'), + scope.identifier('Bar', 'package:foo/foo.dart'), + scope.identifier('Baz', 'package:baz/baz.dart'), + ].map((i) => i.toSource()); + + expect( + identifiers, + [ + 'Foo', + 'Bar', + 'Baz', + ], + ); + + expect( + scope.toImports().map((i) => i.buildAst().toSource()), + [ + r"import 'package:foo/foo.dart';", + r"import 'package:baz/baz.dart';", + ], + ); + }); - expect( - scope.getImports().map/**/((i) => i.toAst().toSource()), - [ - r"import 'package:foo/foo.dart' as _i1;", - r"import 'package:baz/baz.dart' as _i2;", - ], - ); - }); + test('Default scope should auto-prefix', () { + scope = new Scope(); + + var identifiers = [ + scope.identifier('Foo', 'package:foo/foo.dart'), + scope.identifier('Bar', 'package:foo/foo.dart'), + scope.identifier('Baz', 'package:baz/baz.dart'), + ].map((i) => i.toSource()); + + expect( + identifiers, + [ + '_i1.Foo', + '_i1.Bar', + '_i2.Baz', + ], + ); + + expect( + scope.toImports().map((i) => i.buildAst().toSource()), + [ + r"import 'package:foo/foo.dart' as _i1;", + r"import 'package:baz/baz.dart' as _i2;", + ], + ); }); } diff --git a/tool/presubmit.sh b/tool/presubmit.sh new file mode 100755 index 0000000..035e29e --- /dev/null +++ b/tool/presubmit.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Make sure dartfmt is run on everything +# This assumes you have dart_style as a dev_dependency +echo "Checking dartfmt..." +NEEDS_DARTFMT="$(find lib test -name "*.dart" | xargs pub run dart_style:format -n)" +if [[ ${NEEDS_DARTFMT} != "" ]] +then + echo "FAILED" + echo "${NEEDS_DARTFMT}" + exit 1 +fi +echo "PASSED" + +# Make sure we pass the analyzer +echo "Checking dartanalyzer..." +FAILS_ANALYZER="$(find lib test -name "*.dart" | xargs dartanalyzer --options analysis_options.yaml)" +if [[ $FAILS_ANALYZER == *"[error]"* ]] +then + echo "FAILED" + echo "${FAILS_ANALYZER}" + exit 1 +fi +echo "PASSED" + +# Fail on anything that fails going forward. +set -e + +pub run test diff --git a/tool/travis.sh b/tool/travis.sh deleted file mode 100755 index cab493d..0000000 --- a/tool/travis.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file -# for details. All rights reserved. Use of this source code is governed by a -# BSD-style license that can be found in the LICENSE file. - -# Fast fail the script on failures. -set -e - -# Verify that the libraries are error free. -dartanalyzer --fatal-warnings \ - lib/code_builder.dart \ - lib/dart/core.dart \ - lib/testing/equals_source.dart - -# Run the tests. -pub run test - -# Install dart_coveralls; gather and send coverage data. -if [ "$COVERALLS_TOKEN" ] && [ "$TRAVIS_DART_VERSION" = "stable" ]; then - dart tool/create_test_all.dart - pub global activate dart_coveralls - pub global run dart_coveralls report \ - --retry 2 \ - --exclude-test-files \ - tool/test_all.dart - rm tool/test_all.dart -fi