Skip to content
This repository has been archived by the owner on Nov 1, 2024. It is now read-only.

Commit

Permalink
Cleanup: Make all ASTs lazy (no _AbstractCodeBuilder) (#12)
Browse files Browse the repository at this point in the history
* Make all builders lazy, remove AST cloning.

* Fix breakages due to bad refactor of FileBuilder

* Small pubspec increment

* Update pubspec to allow pub publish.

* Add headers, upstream analyzer patch API

* Address comments.

* Fix outstanding lints.
  • Loading branch information
matanlurey authored Sep 19, 2016
1 parent 8dc8d48 commit 1fafa99
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 122 deletions.
66 changes: 58 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,35 @@
[![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)

Code builder is a fluent Dart API for generating valid Dart source code.
`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 generation was traditionally done through a series of
package-specific string concatenations which usually results in messy
and sometimes invalid Dart code that is not easily readable and is very
difficult to refactor.

Code builder uses the [analyzer](analyzer) package to create real Dart
`code_builder` uses the [analyzer](analyzer) package to create real Dart
language ASTs, which, while not guaranteed to be correct, always follows
the analyzer's own understood format.

[analyzer]: https://pub.dartlang.org/packages/analyzer

Code builder also adds a more narrow and user-friendly API. For example
creating a class with a method is an easy affair:
## Experimental

While `code_builder` is considered *stable*, the APIs are subject to
frequent breaking change - a number of Dart language features are not
yet implemented that make it unsuitable for all forms of code
generation.

**Contributions are [welcome][welcome]!**

[welcome]: CONTRIBUTING.md

## Usage

Code builder has a narrow and user-friendly API.

For example creating a class with a method:

```dart
new ClassBuilder('Animal', extends: 'Organism')
Expand All @@ -32,4 +47,39 @@ class Animal extends Organism {
}
```

This package is in development and APIs are subject to frequent change.
Have a complicated set of dependencies for your generated code?
`code_builder` supports automatic scoping of your ASTs to automatically
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',
),
)));
```

Outputs:
```dart
import 'package:thing/thing.dart' as _i1;
import 'package:thing/alternative.dart' as _i2;
_i1.Thing doThing() {}
_i2.Thing doOtherThing(_i1.Thing thing) {}
```
21 changes: 2 additions & 19 deletions lib/code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import 'package:analyzer/src/dart/ast/token.dart';
import 'package:dart_style/dart_style.dart';
import 'package:meta/meta.dart';

import 'src/analyzer_patch.dart';

part 'src/builders/annotation_builder.dart';
part 'src/builders/class_builder.dart';
part 'src/builders/constructor_builder.dart';
Expand All @@ -54,11 +56,6 @@ final DartFormatter _dartfmt = new DartFormatter();
@visibleForTesting
String dartfmt(String source) => _dartfmt.format(source);

// Creates a deep copy of an AST node.
AstNode/*=E*/ _cloneAst/*<E extends AstNode>*/(AstNode/*=E*/ astNode) {
return new AstCloner().cloneNode/*<E>*/(astNode);
}

Identifier _stringIdentifier(String s) {
return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0));
}
Expand All @@ -74,17 +71,3 @@ abstract class CodeBuilder<A extends AstNode> {
/// Uses [scope] to output an AST re-written to use appropriate prefixes.
A toAst([Scope scope = const Scope.identity()]);
}

@Deprecated('Builders are all becoming lazy')
abstract class _AbstractCodeBuilder<A extends AstNode> extends CodeBuilder<A> {
final A _astNode;

_AbstractCodeBuilder._(this._astNode);

/// Returns a copy-safe [AstNode] representing the current builder state.
@override
A toAst([_]) => _cloneAst/*<A>*/(_astNode);

@override
String toString() => '$runtimeType: ${_astNode.toSource()}';
}
65 changes: 65 additions & 0 deletions lib/src/analyzer_patch.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/src/generated/java_core.dart';

/// Implements both old-API [PrintWriter] and new-API [StringBuffer].
///
/// This makes it easier to re-use our `pretty_printer` until analyzer updates.
class PrintBuffer implements PrintWriter, StringBuffer {
final StringBuffer _impl = new StringBuffer();

@override
void clear() {}

@override
bool get isEmpty => _impl.isEmpty;

@override
bool get isNotEmpty => _impl.isNotEmpty;

@override
int get length => _impl.length;

@override
void newLine() {
_impl.writeln();
}

@override
void print(x) {
_impl.write(x);
}

@override
void printf(String fmt, List args) => throw new UnimplementedError();

@override
void println(String s) {
_impl.writeln(s);
}

@override
void write(Object obj) {
_impl.write(obj);
}

@override
void writeAll(Iterable objects, [String separator = ""]) {
_impl.writeAll(objects);
}

@override
void writeCharCode(int charCode) {
_impl.writeCharCode(charCode);
}

@override
void writeln([Object obj = ""]) {
_impl.writeln(obj);
}

@override
String toString() => _impl.toString();
}
3 changes: 2 additions & 1 deletion lib/src/builders/class_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ClassBuilder implements CodeBuilder<ClassDeclaration> {
/// Create a new builder for a `class` named [name].
///
/// Optionally, define another class to [extend] or classes to either
/// [implement] or [mixin]. You may also define a `class` as [abstract].
/// [implement] or [mixin].
factory ClassBuilder(
String name, {
TypeBuilder extend,
Expand All @@ -40,6 +40,7 @@ class ClassBuilder implements CodeBuilder<ClassDeclaration> {
new List<TypeBuilder>.unmodifiable(mixin),
);

/// Create a new builder for an `abstract class` named [name].
factory ClassBuilder.asAbstract(String name,
{TypeBuilder extend,
Iterable<TypeBuilder> implement: const [],
Expand Down
6 changes: 5 additions & 1 deletion lib/src/builders/constructor_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ class ConstructorBuilder implements CodeBuilder<ConstructorDeclaration> {
final String _name;
final List<ParameterBuilder> _parameters = <ParameterBuilder>[];

/// 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 [parameter].
/// Lazily adds [builder].
///
/// When the method is emitted as an AST, [ParameterBuilder.toAst] is used.
void addParameter(ParameterBuilder builder) {
Expand Down
9 changes: 6 additions & 3 deletions lib/src/builders/expression_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ ExpressionBuilder _invokeSelfImpl(
abstract class ExpressionBuilder implements CodeBuilder<Expression> {
/// Invoke [name] (which should be available in the local scope).
///
/// Optionally specify [positional] and [named] arguments.
/// May specify [positional] and [named] arguments.
factory ExpressionBuilder.invoke(
String name, {
String importFrom,
Expand All @@ -91,6 +91,9 @@ abstract class ExpressionBuilder implements CodeBuilder<Expression> {
);
}

/// 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,
Expand Down Expand Up @@ -247,7 +250,7 @@ class _InvokeExpression extends ExpressionBuilder {
}

@override
StatementBuilder toStatement() => new StatementBuilder.fromExpression(this);
StatementBuilder toStatement() => new _ExpressionStatementBuilder(this);

ArgumentList _getArgumentList(Scope scope) {
return new ArgumentList(
Expand Down Expand Up @@ -289,7 +292,7 @@ abstract class _LiteralExpression<A extends Literal>
_asFunctionExpression(this, scope);

@override
StatementBuilder toStatement() => new StatementBuilder.fromExpression(this);
StatementBuilder toStatement() => new _ExpressionStatementBuilder(this);
}

class _LiteralNull extends _LiteralExpression<NullLiteral> {
Expand Down
Loading

0 comments on commit 1fafa99

Please sign in to comment.