Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.

Commit 8dc8d48

Browse files
authored
Add full scoping support for Library (#11)
* Add full scoping support for Library * Address comments.
1 parent cde2a56 commit 8dc8d48

19 files changed

+602
-441
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 0.1.1
4+
5+
- Add concept of `Scope` and change `toAst` to support it
6+
7+
Now your entire AST tree can be scoped and import directives
8+
automatically added to a `LibraryBuilder` for you if you use
9+
`LibraryBuilder.scope`.
10+
311
## 0.1.0
412

513
- Initial version

lib/code_builder.dart

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -44,60 +44,47 @@ part 'src/builders/method_builder.dart';
4444
part 'src/builders/parameter_builder.dart';
4545
part 'src/builders/statement_builder.dart';
4646
part 'src/builders/type_builder.dart';
47-
4847
part 'src/pretty_printer.dart';
4948
part 'src/scope.dart';
5049

50+
final DartFormatter _dartfmt = new DartFormatter();
51+
52+
// Simplifies some of the builders by having a mutable node we clone from.
53+
/// Returns [source] formatted by `dartfmt`.
54+
@visibleForTesting
55+
String dartfmt(String source) => _dartfmt.format(source);
56+
57+
// Creates a deep copy of an AST node.
58+
AstNode/*=E*/ _cloneAst/*<E extends AstNode>*/(AstNode/*=E*/ astNode) {
59+
return new AstCloner().cloneNode/*<E>*/(astNode);
60+
}
61+
62+
Identifier _stringIdentifier(String s) {
63+
return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0));
64+
}
65+
66+
Literal _stringLiteral(String s) {
67+
return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s);
68+
}
69+
5170
/// Base class for building and emitting a Dart language [AstNode].
5271
abstract class CodeBuilder<A extends AstNode> {
5372
/// Returns a copy-safe [AstNode] representing the current builder state.
54-
A toAst();
73+
///
74+
/// Uses [scope] to output an AST re-written to use appropriate prefixes.
75+
A toAst([Scope scope = const Scope.identity()]);
5576
}
5677

57-
// Simplifies some of the builders by having a mutable node we clone from.
78+
@Deprecated('Builders are all becoming lazy')
5879
abstract class _AbstractCodeBuilder<A extends AstNode> extends CodeBuilder<A> {
5980
final A _astNode;
6081

6182
_AbstractCodeBuilder._(this._astNode);
6283

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

6788
@override
6889
String toString() => '$runtimeType: ${_astNode.toSource()}';
6990
}
70-
71-
/// Marker interface for builders that need an import to work.
72-
///
73-
/// **NOTE**: This currently (as of 0.2.0) has no effect. It is planned that
74-
/// the [FileBuilder] will be able to act as a scope 'resolver' and subtly
75-
/// rewrite the AST tree to use prefixing if required (or requested).
76-
abstract class ScopeAware<A extends AstNode> implements CodeBuilder<A> {
77-
@override
78-
A toAst() => toScopedAst(const Scope.identity());
79-
80-
/// Creates a copy-safe [AstNode] representing the current builder state.
81-
///
82-
/// Uses [scope] to output an AST re-written to use appropriate prefixes.
83-
A toScopedAst(Scope scope) => throw new UnimplementedError();
84-
}
85-
86-
// Creates a defensive copy of an AST node.
87-
AstNode/*=E*/ _cloneAst/*<E extends AstNode>*/(AstNode/*=E*/ astNode) {
88-
return new AstCloner().cloneNode/*<E>*/(astNode);
89-
}
90-
91-
final DartFormatter _dartfmt = new DartFormatter();
92-
93-
/// Returns [source] formatted by `dartfmt`.
94-
@visibleForTesting
95-
String dartfmt(String source) => _dartfmt.format(source);
96-
97-
Literal _stringLit(String s) {
98-
return new SimpleStringLiteral(new StringToken(TokenType.STRING, s, 0), s);
99-
}
100-
101-
Identifier _stringId(String s) {
102-
return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0));
103-
}

lib/src/builders/annotation_builder.dart

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ part of code_builder;
1616
/// void destroyTheWorld() { ... }
1717
///
1818
/// To create a `@DoNotUse('Blows up')` use [AnnotationBuilder.invoke].
19-
abstract class AnnotationBuilder implements ScopeAware<Annotation> {
19+
abstract class AnnotationBuilder implements CodeBuilder<Annotation> {
2020
/// Create a new annotated `const` [constructor] invocation.
2121
///
2222
/// May optionally specify an [importFrom] to auto-prefix the annotation _if_
@@ -46,8 +46,7 @@ abstract class AnnotationBuilder implements ScopeAware<Annotation> {
4646
[String importFrom]) = _ReferenceAnnotationBuilder;
4747
}
4848

49-
class _ConstructorAnnotationBuilder extends ScopeAware<Annotation>
50-
implements AnnotationBuilder {
49+
class _ConstructorAnnotationBuilder implements AnnotationBuilder {
5150
final String _constructor;
5251
final ExpressionBuilder _expression;
5352
final String _importFrom;
@@ -56,15 +55,12 @@ class _ConstructorAnnotationBuilder extends ScopeAware<Annotation>
5655
[this._importFrom]);
5756

5857
@override
59-
List<String> get requiredImports => [_importFrom];
60-
61-
@override
62-
Annotation toAst() {
63-
var expressionAst = _expression.toAst();
58+
Annotation toAst([Scope scope = const Scope.identity()]) {
59+
var expressionAst = _expression.toAst(scope);
6460
if (expressionAst is MethodInvocation) {
6561
return new Annotation(
6662
null,
67-
_stringId(_constructor),
63+
scope.getIdentifier(_constructor, _importFrom),
6864
null,
6965
null,
7066
// TODO(matanl): InvocationExpression needs to be public API.
@@ -82,17 +78,11 @@ class _ReferenceAnnotationBuilder implements AnnotationBuilder {
8278
const _ReferenceAnnotationBuilder(this._reference, [this._importFrom]);
8379

8480
@override
85-
List<String> get requiredImports => [_importFrom];
86-
87-
@override
88-
Annotation toAst() => new Annotation(
81+
Annotation toAst([Scope scope = const Scope.identity()]) => new Annotation(
8982
null,
90-
_stringId(_reference),
83+
scope.getIdentifier(_reference, _importFrom),
9184
null,
9285
null,
9386
null,
9487
);
95-
96-
@override
97-
Annotation toScopedAst(_) => throw new UnimplementedError();
9888
}

lib/src/builders/class_builder.dart

Lines changed: 76 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,107 @@
55
part of code_builder;
66

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

14+
final String _name;
15+
final bool _isAbstract;
16+
final TypeBuilder _extend;
17+
final Iterable<TypeBuilder> _implement;
18+
final Iterable<TypeBuilder> _mixin;
19+
20+
final List<FieldBuilder> _fields = <FieldBuilder>[];
21+
final List<MethodBuilder> _methods = <MethodBuilder>[];
22+
final List<AnnotationBuilder> _metadata = <AnnotationBuilder>[];
23+
final List<ConstructorBuilder> _constructors = <ConstructorBuilder>[];
24+
1425
/// Create a new builder for a `class` named [name].
1526
///
1627
/// Optionally, define another class to [extend] or classes to either
1728
/// [implement] or [mixin]. You may also define a `class` as [abstract].
1829
factory ClassBuilder(
1930
String name, {
20-
bool abstract: false,
21-
String extend,
22-
Iterable<String> implement: const [],
23-
Iterable<String> mixin: const [],
24-
}) {
25-
var astNode = _emptyClassDeclaration()..name = _stringId(name);
26-
if (abstract) {
27-
astNode.abstractKeyword = _abstract;
28-
}
29-
if (extend != null) {
30-
astNode.extendsClause = new ExtendsClause(
31-
_extends,
32-
new TypeName(
33-
_stringId(extend),
34-
null,
35-
));
36-
}
37-
if (implement.isNotEmpty) {
38-
astNode.implementsClause = new ImplementsClause(
39-
_implements,
40-
implement
41-
.map/*<TypeName>*/((i) => new TypeName(_stringId(i), null))
42-
.toList());
43-
}
44-
if (mixin.isNotEmpty) {
45-
astNode.withClause = new WithClause(
46-
_with,
47-
mixin
48-
.map/*<TypeName>*/((i) => new TypeName(_stringId(i), null))
49-
.toList());
50-
}
51-
return new ClassBuilder._(astNode);
52-
}
31+
TypeBuilder extend,
32+
Iterable<TypeBuilder> implement: const [],
33+
Iterable<TypeBuilder> mixin: const [],
34+
}) =>
35+
new ClassBuilder._(
36+
name,
37+
false,
38+
extend,
39+
new List<TypeBuilder>.unmodifiable(implement),
40+
new List<TypeBuilder>.unmodifiable(mixin),
41+
);
5342

54-
ClassBuilder._(ClassDeclaration astNode) : super._(astNode);
43+
factory ClassBuilder.asAbstract(String name,
44+
{TypeBuilder extend,
45+
Iterable<TypeBuilder> implement: const [],
46+
Iterable<TypeBuilder> mixin: const []}) =>
47+
new ClassBuilder._(
48+
name,
49+
true,
50+
extend,
51+
new List<TypeBuilder>.unmodifiable(implement),
52+
new List<TypeBuilder>.unmodifiable(mixin),
53+
);
54+
55+
ClassBuilder._(
56+
this._name,
57+
this._isAbstract,
58+
this._extend,
59+
this._implement,
60+
this._mixin,
61+
);
5562

5663
/// Adds an annotation [builder] as metadata.
5764
void addAnnotation(AnnotationBuilder builder) {
58-
_astNode.metadata.add(builder.toAst());
65+
_metadata.add(builder);
5966
}
6067

6168
/// Adds a constructor [builder].
6269
void addConstructor(ConstructorBuilder builder) {
63-
var astNode = builder.toAst();
64-
if (astNode.returnType == null) {
65-
astNode.returnType = _astNode.name;
66-
}
67-
_astNode.members.add(astNode);
70+
_constructors.add(builder);
6871
}
6972

7073
/// Adds a field [builder] as a member on the class.
71-
void addField(FieldBuilder builder, {bool static: false}) {
72-
_astNode.members.add(builder.toFieldAst(static: static));
74+
void addField(FieldBuilder builder) {
75+
_fields.add(builder);
7376
}
7477

7578
/// Adds a method [builder] as a member on the class.
76-
void addMethod(MethodBuilder builder, {bool static: false}) {
77-
_astNode.members.add(builder.toMethodAst(
78-
static: static,
79-
canBeAbstract: _astNode.abstractKeyword != null,
80-
));
79+
void addMethod(MethodBuilder builder) {
80+
_methods.add(builder);
81+
}
82+
83+
@override
84+
ClassDeclaration toAst([Scope scope = const Scope.identity()]) {
85+
var astNode = _emptyClassDeclaration()..name = _stringIdentifier(_name);
86+
if (_isAbstract) {
87+
astNode.abstractKeyword = _abstract;
88+
}
89+
if (_extend != null) {
90+
astNode.extendsClause = new ExtendsClause(_extends, _extend.toAst(scope));
91+
}
92+
if (_implement.isNotEmpty) {
93+
astNode.implementsClause = new ImplementsClause(_implements,
94+
_implement.map/*<TypeName>*/((i) => i.toAst(scope)).toList());
95+
}
96+
if (_mixin.isNotEmpty) {
97+
astNode.withClause = new WithClause(
98+
_with, _mixin.map/*<TypeName>*/((i) => i.toAst(scope)).toList());
99+
}
100+
astNode
101+
..metadata.addAll(_metadata.map/*<Annotation>*/((a) => a.toAst(scope)));
102+
astNode
103+
..members.addAll(_fields.map/*<ClassMember>*/((f) => f.toFieldAst(scope)))
104+
..members.addAll(_constructors.map/*<ClassMember>*/(
105+
(c) => c.toAst(scope)..returnType = _stringIdentifier(_name)))
106+
..members
107+
.addAll(_methods.map/*<ClassMember>*/((m) => m.toMethodAst(scope)));
108+
return astNode;
81109
}
82110

83111
static ClassDeclaration _emptyClassDeclaration() => new ClassDeclaration(

lib/src/builders/constructor_builder.dart

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,51 @@ part of code_builder;
66

77
/// Builds a [ConstructorDeclaration] AST.
88
///
9-
/// Similar to [MethodBuilder] but adds constructor-only features.
10-
///
11-
/// Use [ConstructorBuilder.initializeFields] to create something like:
12-
/// class Foo {
13-
/// final _one;
14-
/// final _two;
15-
///
16-
/// Foo(this._one, this._two);
17-
/// }
18-
class ConstructorBuilder extends _AbstractCodeBuilder<ConstructorDeclaration> {
9+
/// Similar to [MethodBuilder] but with constructor-only features.
10+
class ConstructorBuilder implements CodeBuilder<ConstructorDeclaration> {
1911
static final Token _const = new KeywordToken(Keyword.CONST, 0);
2012
static final Token _this = new KeywordToken(Keyword.THIS, 0);
2113

22-
/// Create a simple [ConstructorBuilder] that initializes class fields.
14+
final bool _isConstant;
15+
final String _name;
16+
final List<ParameterBuilder> _parameters = <ParameterBuilder>[];
17+
18+
factory ConstructorBuilder([String name]) {
19+
return new ConstructorBuilder._(false, name);
20+
}
21+
22+
factory ConstructorBuilder.isConst([String name]) {
23+
return new ConstructorBuilder._(true, name);
24+
}
25+
26+
ConstructorBuilder._(this._isConstant, this._name);
27+
28+
/// Lazily adds [parameter].
2329
///
24-
/// May optionally be [constant] compatible, or have a [name].
25-
factory ConstructorBuilder.initializeFields(
26-
{bool constant: false,
27-
String name,
28-
Iterable<String> positionalArguments: const [],
29-
Iterable<String> optionalArguments: const [],
30-
Iterable<String> namedArguments: const []}) {
31-
var parameters = <FormalParameter>[];
32-
for (var a in positionalArguments) {
33-
parameters.add(new ParameterBuilder(a, field: true).toAst());
34-
}
35-
for (var a in optionalArguments) {
36-
parameters.add(new ParameterBuilder.optional(a, field: true).toAst());
37-
}
38-
for (var a in namedArguments) {
39-
parameters.add(new ParameterBuilder.named(a, field: true).toAst());
40-
}
30+
/// When the method is emitted as an AST, [ParameterBuilder.toAst] is used.
31+
void addParameter(ParameterBuilder builder) {
32+
_parameters.add(builder);
33+
}
34+
35+
@override
36+
ConstructorDeclaration toAst([Scope scope = const Scope.identity()]) {
4137
var astNode = new ConstructorDeclaration(
4238
null,
4339
null,
4440
null,
4541
null,
46-
constant ? _const : null,
42+
_isConstant ? _const : null,
4743
null,
4844
null,
49-
name != null ? _stringId(name) : null,
50-
MethodBuilder._emptyParameters()..parameters.addAll(parameters),
45+
_name != null ? _stringIdentifier(_name) : null,
46+
MethodBuilder._emptyParameters()
47+
..parameters.addAll(
48+
_parameters.map/*<FormalParameter>*/((p) => p.toAst(scope))),
5149
null,
5250
null,
5351
null,
5452
new EmptyFunctionBody(MethodBuilder._semicolon),
5553
);
56-
return new ConstructorBuilder._(astNode);
54+
return astNode;
5755
}
58-
59-
ConstructorBuilder._(ConstructorDeclaration astNode) : super._(astNode);
6056
}

0 commit comments

Comments
 (0)