Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.1.1

- Add concept of `Scope` and change `toAst` to support it

Now your entire AST tree can be scoped and import directives
automatically added to a `LibraryBuilder` for you if you use
`LibraryBuilder.scope`.

## 0.1.0

- Initial version
63 changes: 25 additions & 38 deletions lib/code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,60 +44,47 @@ part 'src/builders/method_builder.dart';
part 'src/builders/parameter_builder.dart';
part 'src/builders/statement_builder.dart';
part 'src/builders/type_builder.dart';

part 'src/pretty_printer.dart';
part 'src/scope.dart';

final DartFormatter _dartfmt = new DartFormatter();

// Simplifies some of the builders by having a mutable node we clone from.
/// Returns [source] formatted by `dartfmt`.
@visibleForTesting
String dartfmt(String source) => _dartfmt.format(source);

// Creates a 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));
}

Literal _stringLiteral(String s) {
return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s);
}

/// Base class for building and emitting a Dart language [AstNode].
abstract class CodeBuilder<A extends AstNode> {
/// Returns a copy-safe [AstNode] representing the current builder state.
A toAst();
///
/// Uses [scope] to output an AST re-written to use appropriate prefixes.
A toAst([Scope scope = const Scope.identity()]);
}

// Simplifies some of the builders by having a mutable node we clone from.
@Deprecated('Builders are all becoming lazy')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth deprecating an internal class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's helping me (in the analyzer) show what work I have left to do

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);
A toAst([_]) => _cloneAst/*<A>*/(_astNode);

@override
String toString() => '$runtimeType: ${_astNode.toSource()}';
}

/// Marker interface for builders that need an import to work.
///
/// **NOTE**: This currently (as of 0.2.0) has no effect. It is planned that
/// the [FileBuilder] will be able to act as a scope 'resolver' and subtly
/// rewrite the AST tree to use prefixing if required (or requested).
abstract class ScopeAware<A extends AstNode> implements CodeBuilder<A> {
@override
A toAst() => toScopedAst(const Scope.identity());

/// Creates a copy-safe [AstNode] representing the current builder state.
///
/// Uses [scope] to output an AST re-written to use appropriate prefixes.
A toScopedAst(Scope scope) => throw new UnimplementedError();
}

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

final DartFormatter _dartfmt = new DartFormatter();

/// Returns [source] formatted by `dartfmt`.
@visibleForTesting
String dartfmt(String source) => _dartfmt.format(source);

Literal _stringLit(String s) {
return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s);
}

Identifier _stringId(String s) {
return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0));
}
24 changes: 7 additions & 17 deletions lib/src/builders/annotation_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ part of code_builder;
/// void destroyTheWorld() { ... }
///
/// To create a `@DoNotUse('Blows up')` use [AnnotationBuilder.invoke].
abstract class AnnotationBuilder implements ScopeAware<Annotation> {
abstract class AnnotationBuilder implements CodeBuilder<Annotation> {
/// Create a new annotated `const` [constructor] invocation.
///
/// May optionally specify an [importFrom] to auto-prefix the annotation _if_
Expand Down Expand Up @@ -46,8 +46,7 @@ abstract class AnnotationBuilder implements ScopeAware<Annotation> {
[String importFrom]) = _ReferenceAnnotationBuilder;
}

class _ConstructorAnnotationBuilder extends ScopeAware<Annotation>
implements AnnotationBuilder {
class _ConstructorAnnotationBuilder implements AnnotationBuilder {
final String _constructor;
final ExpressionBuilder _expression;
final String _importFrom;
Expand All @@ -56,15 +55,12 @@ class _ConstructorAnnotationBuilder extends ScopeAware<Annotation>
[this._importFrom]);

@override
List<String> get requiredImports => [_importFrom];

@override
Annotation toAst() {
var expressionAst = _expression.toAst();
Annotation toAst([Scope scope = const Scope.identity()]) {
var expressionAst = _expression.toAst(scope);
if (expressionAst is MethodInvocation) {
return new Annotation(
null,
_stringId(_constructor),
scope.getIdentifier(_constructor, _importFrom),
null,
null,
// TODO(matanl): InvocationExpression needs to be public API.
Expand All @@ -82,17 +78,11 @@ class _ReferenceAnnotationBuilder implements AnnotationBuilder {
const _ReferenceAnnotationBuilder(this._reference, [this._importFrom]);

@override
List<String> get requiredImports => [_importFrom];

@override
Annotation toAst() => new Annotation(
Annotation toAst([Scope scope = const Scope.identity()]) => new Annotation(
null,
_stringId(_reference),
scope.getIdentifier(_reference, _importFrom),
null,
null,
null,
);

@override
Annotation toScopedAst(_) => throw new UnimplementedError();
}
124 changes: 76 additions & 48 deletions lib/src/builders/class_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,79 +5,107 @@
part of code_builder;

/// Builds a [ClassDeclaration] AST.
class ClassBuilder extends _AbstractCodeBuilder<ClassDeclaration> {
class ClassBuilder implements CodeBuilder<ClassDeclaration> {
static Token _abstract = new KeywordToken(Keyword.ABSTRACT, 0);
static Token _extends = new KeywordToken(Keyword.EXTENDS, 0);
static Token _implements = new KeywordToken(Keyword.IMPLEMENTS, 0);
static Token _with = new KeywordToken(Keyword.WITH, 0);

final String _name;
final bool _isAbstract;
final TypeBuilder _extend;
final Iterable<TypeBuilder> _implement;
final Iterable<TypeBuilder> _mixin;

final List<FieldBuilder> _fields = <FieldBuilder>[];
final List<MethodBuilder> _methods = <MethodBuilder>[];
final List<AnnotationBuilder> _metadata = <AnnotationBuilder>[];
final List<ConstructorBuilder> _constructors = <ConstructorBuilder>[];

/// 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].
factory ClassBuilder(
String name, {
bool abstract: false,
String extend,
Iterable<String> implement: const [],
Iterable<String> mixin: const [],
}) {
var astNode = _emptyClassDeclaration()..name = _stringId(name);
if (abstract) {
astNode.abstractKeyword = _abstract;
}
if (extend != null) {
astNode.extendsClause = new ExtendsClause(
_extends,
new TypeName(
_stringId(extend),
null,
));
}
if (implement.isNotEmpty) {
astNode.implementsClause = new ImplementsClause(
_implements,
implement
.map/*<TypeName>*/((i) => new TypeName(_stringId(i), null))
.toList());
}
if (mixin.isNotEmpty) {
astNode.withClause = new WithClause(
_with,
mixin
.map/*<TypeName>*/((i) => new TypeName(_stringId(i), null))
.toList());
}
return new ClassBuilder._(astNode);
}
TypeBuilder extend,
Iterable<TypeBuilder> implement: const [],
Iterable<TypeBuilder> mixin: const [],
}) =>
new ClassBuilder._(
name,
false,
extend,
new List<TypeBuilder>.unmodifiable(implement),
new List<TypeBuilder>.unmodifiable(mixin),
);

ClassBuilder._(ClassDeclaration astNode) : super._(astNode);
factory ClassBuilder.asAbstract(String name,
{TypeBuilder extend,
Iterable<TypeBuilder> implement: const [],
Iterable<TypeBuilder> mixin: const []}) =>
new ClassBuilder._(
name,
true,
extend,
new List<TypeBuilder>.unmodifiable(implement),
new List<TypeBuilder>.unmodifiable(mixin),
);

ClassBuilder._(
this._name,
this._isAbstract,
this._extend,
this._implement,
this._mixin,
);

/// Adds an annotation [builder] as metadata.
void addAnnotation(AnnotationBuilder builder) {
_astNode.metadata.add(builder.toAst());
_metadata.add(builder);
}

/// Adds a constructor [builder].
void addConstructor(ConstructorBuilder builder) {
var astNode = builder.toAst();
if (astNode.returnType == null) {
astNode.returnType = _astNode.name;
}
_astNode.members.add(astNode);
_constructors.add(builder);
}

/// Adds a field [builder] as a member on the class.
void addField(FieldBuilder builder, {bool static: false}) {
_astNode.members.add(builder.toFieldAst(static: static));
void addField(FieldBuilder builder) {
_fields.add(builder);
}

/// Adds a method [builder] as a member on the class.
void addMethod(MethodBuilder builder, {bool static: false}) {
_astNode.members.add(builder.toMethodAst(
static: static,
canBeAbstract: _astNode.abstractKeyword != null,
));
void addMethod(MethodBuilder builder) {
_methods.add(builder);
}

@override
ClassDeclaration toAst([Scope scope = const Scope.identity()]) {
var astNode = _emptyClassDeclaration()..name = _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/*<TypeName>*/((i) => i.toAst(scope)).toList());
}
if (_mixin.isNotEmpty) {
astNode.withClause = new WithClause(
_with, _mixin.map/*<TypeName>*/((i) => i.toAst(scope)).toList());
}
astNode
..metadata.addAll(_metadata.map/*<Annotation>*/((a) => a.toAst(scope)));
astNode
..members.addAll(_fields.map/*<ClassMember>*/((f) => f.toFieldAst(scope)))
..members.addAll(_constructors.map/*<ClassMember>*/(
(c) => c.toAst(scope)..returnType = _stringIdentifier(_name)))
..members
.addAll(_methods.map/*<ClassMember>*/((m) => m.toMethodAst(scope)));
return astNode;
}

static ClassDeclaration _emptyClassDeclaration() => new ClassDeclaration(
Expand Down
64 changes: 30 additions & 34 deletions lib/src/builders/constructor_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,51 @@ part of code_builder;

/// Builds a [ConstructorDeclaration] AST.
///
/// Similar to [MethodBuilder] but adds constructor-only features.
///
/// Use [ConstructorBuilder.initializeFields] to create something like:
/// class Foo {
/// final _one;
/// final _two;
///
/// Foo(this._one, this._two);
/// }
class ConstructorBuilder extends _AbstractCodeBuilder<ConstructorDeclaration> {
/// Similar to [MethodBuilder] but with constructor-only features.
class ConstructorBuilder implements CodeBuilder<ConstructorDeclaration> {
static final Token _const = new KeywordToken(Keyword.CONST, 0);
static final Token _this = new KeywordToken(Keyword.THIS, 0);

/// Create a simple [ConstructorBuilder] that initializes class fields.
final bool _isConstant;
final String _name;
final List<ParameterBuilder> _parameters = <ParameterBuilder>[];

factory ConstructorBuilder([String name]) {
return new ConstructorBuilder._(false, name);
}

factory ConstructorBuilder.isConst([String name]) {
return new ConstructorBuilder._(true, name);
}

ConstructorBuilder._(this._isConstant, this._name);

/// Lazily adds [parameter].
///
/// May optionally be [constant] compatible, or have a [name].
factory ConstructorBuilder.initializeFields(
{bool constant: false,
String name,
Iterable<String> positionalArguments: const [],
Iterable<String> optionalArguments: const [],
Iterable<String> namedArguments: const []}) {
var parameters = <FormalParameter>[];
for (var a in positionalArguments) {
parameters.add(new ParameterBuilder(a, field: true).toAst());
}
for (var a in optionalArguments) {
parameters.add(new ParameterBuilder.optional(a, field: true).toAst());
}
for (var a in namedArguments) {
parameters.add(new ParameterBuilder.named(a, field: true).toAst());
}
/// When the method is emitted as an AST, [ParameterBuilder.toAst] is used.
void addParameter(ParameterBuilder builder) {
_parameters.add(builder);
}

@override
ConstructorDeclaration toAst([Scope scope = const Scope.identity()]) {
var astNode = new ConstructorDeclaration(
null,
null,
null,
null,
constant ? _const : null,
_isConstant ? _const : null,
null,
null,
name != null ? _stringId(name) : null,
MethodBuilder._emptyParameters()..parameters.addAll(parameters),
_name != null ? _stringIdentifier(_name) : null,
MethodBuilder._emptyParameters()
..parameters.addAll(
_parameters.map/*<FormalParameter>*/((p) => p.toAst(scope))),
null,
null,
null,
new EmptyFunctionBody(MethodBuilder._semicolon),
);
return new ConstructorBuilder._(astNode);
return astNode;
}

ConstructorBuilder._(ConstructorDeclaration astNode) : super._(astNode);
}
Loading