From 9d7fee8eff75f39f8b9e5b3be23eecf9a3e34b46 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Sep 2016 14:57:36 -0700 Subject: [PATCH 1/5] Add a simple identifier Scope class and a test. --- lib/code_builder.dart | 1 + lib/src/scope.dart | 65 +++++++++++++++++++++++++++++++++++++++++++ test/scope_test.dart | 55 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 lib/src/scope.dart create mode 100644 test/scope_test.dart diff --git a/lib/code_builder.dart b/lib/code_builder.dart index 23e4d12..a5b4265 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -46,6 +46,7 @@ part 'src/builders/statement_builder.dart'; part 'src/builders/type_builder.dart'; part 'src/pretty_printer.dart'; +part 'src/scope.dart'; /// Base class for building and emitting a Dart language [AstNode]. abstract class CodeBuilder { diff --git a/lib/src/scope.dart b/lib/src/scope.dart new file mode 100644 index 0000000..f2bc984 --- /dev/null +++ b/lib/src/scope.dart @@ -0,0 +1,65 @@ +part of code_builder; + +/// Determines an [Identifier] given where it appears and where it is from. +/// +/// __Example use__: +/// void useContext(Scope scope) { +/// // Prints Identifier "i1.Foo" +/// print(scope.getIdentifier('Foo', 'package:foo/foo.dart'); +/// +/// // Prints Identifier "i1.Bar" +/// print(scope.getIdentifier('Bar', 'package:foo/foo.dart'); +/// +/// // Prints Identifier "i2.Baz" +/// print(scope.getIdentifier('Baz', 'package:bar/bar.dart'); +/// } +abstract class Scope { + /// Create a default scope context. + /// + /// 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; + + /// Create a context that does _not_ apply any scoping. + factory Scope.identity() = _IdentityScope; + + /// Given a [symbol] and it's known [importUri], return an [Identifier]. + Identifier getIdentifier(String symbol, String importUri); + + /// Returns a list of all imports needed to resolve identifiers. + Iterable getImports(); +} + +class _IdentityScope implements Scope { + final Set _imports = new Set(); + + @override + Identifier getIdentifier(String symbol, String import) { + _imports.add(import); + return _stringId(symbol); + } + + @override + Iterable getImports() { + return _imports.map/* new ImportBuilder(i)); + } +} + +class _IncrementingScope implements Scope { + final Map _imports = {}; + + int _counter = 0; + + @override + Identifier getIdentifier(String symbol, String import) { + var newId = _imports.putIfAbsent(import, () => ++_counter); + return new PrefixedIdentifier(_stringId('_i$newId'), + new Token(TokenType.PERIOD, 0), _stringId(symbol)); + } + + @override + Iterable getImports() { + return _imports.keys.map/* new ImportBuilder(i, as: '_i${_imports[i]}')); + } +} diff --git a/test/scope_test.dart b/test/scope_test.dart new file mode 100644 index 0000000..db32ead --- /dev/null +++ b/test/scope_test.dart @@ -0,0 +1,55 @@ +import 'package:analyzer/analyzer.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:test/test.dart'; + +void main() { + group('Identity scope', () { + Scope scope; + + setUp(() => scope = new 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()); + + 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';", + ],); + }); + }); + + 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', + ],); + + expect(scope.getImports().map/**/((i) => i.toAst().toSource()), [ + r"import 'package:foo/foo.dart' as _i1;", + r"import 'package:baz/baz.dart' as _i2;", + ],); + }); + }); +} From 02356a1e37a4adca5cbaf47a0570ab44d960f4d1 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Sep 2016 15:40:21 -0700 Subject: [PATCH 2/5] Address comments. --- lib/src/scope.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/scope.dart b/lib/src/scope.dart index f2bc984..92ead6c 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -1,6 +1,6 @@ part of code_builder; -/// Determines an [Identifier] given where it appears and where it is from. +/// Determines an [Identifier] deppending on where it appears. /// /// __Example use__: /// void useContext(Scope scope) { @@ -23,7 +23,7 @@ abstract class Scope { /// Create a context that does _not_ apply any scoping. factory Scope.identity() = _IdentityScope; - /// Given a [symbol] and it's known [importUri], return an [Identifier]. + /// Given a [symbol] and its known [importUri], return an [Identifier]. Identifier getIdentifier(String symbol, String importUri); /// Returns a list of all imports needed to resolve identifiers. From c2c4bcc0f2368502e4cd700b9004be44a76eab60 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Sep 2016 17:08:11 -0700 Subject: [PATCH 3/5] Add some support for scoping (methods, types, parameters) --- lib/code_builder.dart | 8 +- lib/src/builders/annotation_builder.dart | 4 +- lib/src/builders/file_builder.dart | 30 ++++- lib/src/builders/method_builder.dart | 25 ++-- lib/src/builders/parameter_builder.dart | 155 ++++++++++++++--------- lib/src/builders/type_builder.dart | 11 +- lib/src/scope.dart | 19 ++- lib/testing/equals_source.dart | 54 +++++++- test/builders/method_builder_test.dart | 31 +++++ test/scope_test.dart | 24 +++- 10 files changed, 266 insertions(+), 95 deletions(-) diff --git a/lib/code_builder.dart b/lib/code_builder.dart index a5b4265..fc800e7 100644 --- a/lib/code_builder.dart +++ b/lib/code_builder.dart @@ -73,14 +73,14 @@ abstract class _AbstractCodeBuilder extends CodeBuilder { /// **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 RequiresImport implements CodeBuilder { - /// Imports that are required for this AST to work. - List get requiredImports; +abstract class ScopeAware implements CodeBuilder { + @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(FileBuilder scope) => throw new UnimplementedError(); + A toScopedAst(Scope scope) => throw new UnimplementedError(); } // Creates a defensive copy of an AST node. diff --git a/lib/src/builders/annotation_builder.dart b/lib/src/builders/annotation_builder.dart index 885bc40..a209a46 100644 --- a/lib/src/builders/annotation_builder.dart +++ b/lib/src/builders/annotation_builder.dart @@ -16,7 +16,7 @@ part of code_builder; /// void destroyTheWorld() { ... } /// /// To create a `@DoNotUse('Blows up')` use [AnnotationBuilder.invoke]. -abstract class AnnotationBuilder implements RequiresImport { +abstract class AnnotationBuilder implements ScopeAware { /// Create a new annotated `const` [constructor] invocation. /// /// May optionally specify an [importFrom] to auto-prefix the annotation _if_ @@ -46,7 +46,7 @@ abstract class AnnotationBuilder implements RequiresImport { [String importFrom]) = _ReferenceAnnotationBuilder; } -class _ConstructorAnnotationBuilder extends RequiresImport +class _ConstructorAnnotationBuilder extends ScopeAware implements AnnotationBuilder { final String _constructor; final ExpressionBuilder _expression; diff --git a/lib/src/builders/file_builder.dart b/lib/src/builders/file_builder.dart index 733f716..1d4d8d3 100644 --- a/lib/src/builders/file_builder.dart +++ b/lib/src/builders/file_builder.dart @@ -28,6 +28,8 @@ abstract class FileBuilder extends _AbstractCodeBuilder { class LibraryBuilder extends FileBuilder { static final Token _library = new KeywordToken(Keyword.LIBRARY, 0); + final Scope _scope; + /// Create a new standalone Dart library, optionally with a [name]. factory LibraryBuilder([String name]) { var astNode = _emptyCompilationUnit(); @@ -39,17 +41,37 @@ class LibraryBuilder extends FileBuilder { new LibraryIdentifier([_stringId(name)]), null,)); } - return new LibraryBuilder._(astNode); + return new LibraryBuilder._(astNode, new Scope.dedupe()); } /// Create a new standalone Dart library, optionally with a [name]. /// - /// As references are added in the library that implements [RequiresImport] + /// As references are added in the library that implements [ScopeAware] /// they are re-written to avoid collisions and the imports are automatically /// included at the top with optional prefixes. - factory LibraryBuilder.autoScope({String name}) => new LibraryBuilder(name); + factory LibraryBuilder.scope({String name, Scope scope}) { + var astNode = _emptyCompilationUnit(); + if (name != null) { + astNode.directives.add(new LibraryDirective( + null, + null, + _library, + new LibraryIdentifier([_stringId(name)]), + null,)); + } + return new LibraryBuilder._(astNode, scope ?? new Scope()); + } + + LibraryBuilder._(CompilationUnit astNode, this._scope) : super._(astNode); - LibraryBuilder._(CompilationUnit astNode) : super._(astNode); + @override + void addDeclaration(CodeBuilder declaration) { + if (declaration is ScopeAware) { + _astNode.declarations.add(declaration.toScopedAst(_scope)); + } else { + super.addDeclaration(declaration); + } + } /// Adds [directive]'s resulting AST to the source. void addDirective(CodeBuilder directive) { diff --git a/lib/src/builders/method_builder.dart b/lib/src/builders/method_builder.dart index a501df7..f43b756 100644 --- a/lib/src/builders/method_builder.dart +++ b/lib/src/builders/method_builder.dart @@ -11,7 +11,9 @@ part of code_builder; /// the top level and within other methods) via [toFunctionAst]. /// /// To return nothing (`void`), use [MethodBuilder.returnVoid]. -class MethodBuilder implements CodeBuilder { +class MethodBuilder implements + CodeBuilder, + ScopeAware { static Token _abstract = new KeywordToken(Keyword.ABSTRACT, 0); static Token _semicolon = new Token(TokenType.SEMICOLON, 0); static Token _static = new KeywordToken(Keyword.STATIC, 0); @@ -41,6 +43,9 @@ class MethodBuilder implements CodeBuilder { MethodBuilder._(this._name, this._returnType); + @override + List get requiredImports => null; + /// Lazily adds [annotation]. /// /// When the method is emitted as an AST, [AnnotationBuilder.toAst] is used. @@ -76,14 +81,14 @@ class MethodBuilder implements CodeBuilder { /// [toMethodAst]. @override @visibleForTesting - Declaration toAst() => toFunctionAst(); + Declaration toAst() => toScopedAst(const Scope.identity()); /// Returns a copy-safe [FunctionDeclaration] AST representing current state. - FunctionDeclaration toFunctionAst() { + FunctionDeclaration toFunctionAst({Scope scope: const Scope.identity()}) { var functionAst = _emptyFunction() ..metadata.addAll(_annotations.map/**/((a) => a.toAst())) ..name = _stringId(_name) - ..returnType = _returnType?.toAst(); + ..returnType = _returnType?.toScopedAst(scope); if (_returnExpression != null) { functionAst.functionExpression = _returnExpression.toFunctionExpression(); } else { @@ -95,7 +100,7 @@ class MethodBuilder implements CodeBuilder { } if (_parameters.isNotEmpty) { functionAst.functionExpression.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toAst())); + .addAll(_parameters.map/**/((p) => p.toScopedAst(scope))); } return functionAst; } @@ -104,11 +109,12 @@ class MethodBuilder implements CodeBuilder { MethodDeclaration toMethodAst({ bool static: false, bool canBeAbstract: false, + Scope scope: const Scope.identity(), }) { var methodAst = _emptyMethod() ..metadata.addAll(_annotations.map/**/((a) => a.toAst())) ..name = _stringId(_name) - ..returnType = _returnType?.toAst(); + ..returnType = _returnType?.toScopedAst(scope); FunctionBody methodBody = _returnExpression?.toFunctionBody(); if (static) { methodAst.modifierKeyword = _static; @@ -123,12 +129,15 @@ class MethodBuilder implements CodeBuilder { } if (_parameters.isNotEmpty) { methodAst.parameters.parameters - .addAll(_parameters.map/**/((p) => p.toAst())); + .addAll(_parameters.map/**/((p) => p.toScopedAst(scope))); } methodAst.body = methodBody; return methodAst; } + @override + Declaration toScopedAst(Scope scope) => toFunctionAst(scope: scope); + @override String toString() => 'MethodBuilder ${toAst().toSource()}'; @@ -156,7 +165,7 @@ class MethodBuilder implements CodeBuilder { _blockBody(), ), ); - + // TODO: implement requiredImports static MethodDeclaration _emptyMethod() => new MethodDeclaration( null, null, diff --git a/lib/src/builders/parameter_builder.dart b/lib/src/builders/parameter_builder.dart index bf710d1..16466f6 100644 --- a/lib/src/builders/parameter_builder.dart +++ b/lib/src/builders/parameter_builder.dart @@ -9,44 +9,36 @@ part of code_builder; /// 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 extends _AbstractCodeBuilder { +class ParameterBuilder extends ScopeAware { static final Token _this = new KeywordToken(Keyword.THIS, 0); + 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, }) { - FormalParameter astNode; - if (field) { - astNode = new FieldFormalParameter( - null, - null, - null, - type?.toAst(), - _this, - null, - _stringId(name), - null, - null, - ); - } else { - astNode = new SimpleFormalParameter( - null, - null, - null, - type?.toAst(), - _stringId(name), - ); - } - return new ParameterBuilder._(astNode); + return new ParameterBuilder._( + name, + field, + false, + false, + type, + null, + ); } - /// Create a new _optional_ (but positional) parameter. - /// - /// See [ParameterBuilder.named] for an optional _named_ parameter. - factory ParameterBuilder.optional( + /// Create a new _optional_ _named_ parameter. + factory ParameterBuilder.named( String name, { bool field: false, TypeBuilder type, @@ -54,15 +46,17 @@ class ParameterBuilder extends _AbstractCodeBuilder { }) { return new ParameterBuilder._optional( name, - false, + true, field: field, type: type, defaultTo: defaultTo, ); } - /// Create a new _optional_ _named_ parameter. - factory ParameterBuilder.named( + /// 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, @@ -70,13 +64,22 @@ class ParameterBuilder extends _AbstractCodeBuilder { }) { return new ParameterBuilder._optional( name, - true, + 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, @@ -85,46 +88,76 @@ class ParameterBuilder extends _AbstractCodeBuilder { 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 toScopedAst(Scope scope) { FormalParameter astNode; - if (field) { - astNode = new FieldFormalParameter( + 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; + } + + static DefaultFormalParameter _createDefaultFormalParameter( + FormalParameter parameter, + bool named, + ExpressionBuilder defaultTo, + Scope scope, + ) { + return new DefaultFormalParameter( + parameter, + named ? ParameterKind.NAMED : ParameterKind.POSITIONAL, + defaultTo != null + ? named ? new Token(TokenType.COLON, 0) : new Token(TokenType.EQ, 0) + : null, + defaultTo?.toAst(), + ); + } + + FieldFormalParameter _createFieldFormalParameter(Scope scope) => + new FieldFormalParameter( null, null, null, - type?.toAst(), + _type?.toScopedAst(scope), _this, null, - _stringId(name), + _stringId(_name), null, null, ); - } else { - astNode = new SimpleFormalParameter( + + SimpleFormalParameter _createSimpleFormalParameter(Scope scope) => + new SimpleFormalParameter( null, null, null, - type?.toAst(), - _stringId(name), + _type?.toScopedAst(scope), + _stringId(_name), ); - } - Token defaultToEq; - if (defaultTo != null) { - defaultToEq = - named ? new Token(TokenType.COLON, 0) : new Token(TokenType.EQ, 0); - } - astNode = new DefaultFormalParameter( - astNode, - named ? ParameterKind.NAMED : ParameterKind.POSITIONAL, - defaultToEq, - defaultTo?.toAst(), - ); - return new ParameterBuilder._(astNode); - } - - ParameterBuilder._(FormalParameter astNode) : super._(astNode); - - /// Adds a metadata annotation from [builder]. - void addAnnotation(AnnotationBuilder builder) { - _astNode.metadata.add(builder.toAst()); - } } diff --git a/lib/src/builders/type_builder.dart b/lib/src/builders/type_builder.dart index 45aac32..19025c8 100644 --- a/lib/src/builders/type_builder.dart +++ b/lib/src/builders/type_builder.dart @@ -5,7 +5,7 @@ part of code_builder; /// Build a [TypeName] AST. -class TypeBuilder implements RequiresImport { +class TypeBuilder implements ScopeAware { final String _identifier; final String _importFrom; @@ -22,11 +22,10 @@ class TypeBuilder implements RequiresImport { List get requiredImports => [_importFrom]; @override - TypeName toAst() => new TypeName( - _stringId(_identifier), - null, - ); + TypeName toAst() => toScopedAst(const Scope.identity()); @override - TypeName toScopedAst(FileBuilder scope) => throw new UnimplementedError(); + TypeName toScopedAst(Scope scope) { + return new TypeName(scope.getIdentifier(_identifier, _importFrom), null); + } } diff --git a/lib/src/scope.dart b/lib/src/scope.dart index 92ead6c..a96f326 100644 --- a/lib/src/scope.dart +++ b/lib/src/scope.dart @@ -20,8 +20,11 @@ abstract class Scope { /// will be unique in a given scope (actual implementation may be naive). factory Scope() = _IncrementingScope; - /// Create a context that does _not_ apply any scoping. - factory Scope.identity() = _IdentityScope; + /// Create a context that just de-duplicates imports (no scoping). + factory Scope.dedupe() = _DeduplicatingScope; + + /// Create a context that does nothing. + const factory Scope.identity() = _IdentityScope; /// Given a [symbol] and its known [importUri], return an [Identifier]. Identifier getIdentifier(String symbol, String importUri); @@ -30,7 +33,7 @@ abstract class Scope { Iterable getImports(); } -class _IdentityScope implements Scope { +class _DeduplicatingScope implements Scope { final Set _imports = new Set(); @override @@ -45,6 +48,16 @@ class _IdentityScope implements Scope { } } +class _IdentityScope implements Scope { + const _IdentityScope(); + + @override + Identifier getIdentifier(String symbol, _) => _stringId(symbol); + + @override + Iterable getImports() => const []; +} + class _IncrementingScope implements Scope { final Map _imports = {}; diff --git a/lib/testing/equals_source.dart b/lib/testing/equals_source.dart index a706e7d..5c69e4f 100644 --- a/lib/testing/equals_source.dart +++ b/lib/testing/equals_source.dart @@ -2,6 +2,9 @@ // 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:matcher/matcher.dart'; @@ -16,8 +19,41 @@ import 'package:matcher/matcher.dart'; /// /// 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}) { - return new _EqualsSource(format ? dartfmt(source) : source, format); +Matcher equalsSource(String source, {bool format: true, Scope scope}) { + return new _EqualsSource( + scope, + format ? dartfmt(source) : source, + format, + ); +} + +Identifier _stringId(String s) { + return new SimpleIdentifier(new StringToken(TokenType.STRING, s, 0)); +} + +/// 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(); + +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), + new Token(TokenType.PERIOD, 0), + _stringId(symbol), + ); + } + + @override + Iterable getImports() => const []; } // Delegates to the default matcher (which delegates further). @@ -25,10 +61,11 @@ Matcher equalsSource(String source, {bool format: true}) { // Would be nice to just use more of package:matcher directly: // https://github.com/dart-lang/matcher/issues/36 class _EqualsSource extends Matcher { + final Scope _scope; final String _source; final bool _isFormatted; - _EqualsSource(this._source, this._isFormatted); + _EqualsSource(this._scope, this._source, this._isFormatted); @override Description describe(Description description) { @@ -55,11 +92,16 @@ class _EqualsSource extends Matcher { } String _formatAst(CodeBuilder builder) { - var ast = builder.toAst(); + AstNode astNode; + if (_scope != null && builder is ScopeAware) { + astNode = builder.toScopedAst(_scope); + } else { + astNode = builder.toAst(); + } if (_isFormatted) { - return prettyToSource(ast); + return prettyToSource(astNode); } else { - return ast.toSource(); + return astNode.toSource(); } } diff --git a/test/builders/method_builder_test.dart b/test/builders/method_builder_test.dart index 28bb930..e569657 100644 --- a/test/builders/method_builder_test.dart +++ b/test/builders/method_builder_test.dart @@ -22,6 +22,37 @@ void main() { ); }); + 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()), diff --git a/test/scope_test.dart b/test/scope_test.dart index db32ead..3f45a2f 100644 --- a/test/scope_test.dart +++ b/test/scope_test.dart @@ -6,7 +6,29 @@ void main() { group('Identity scope', () { Scope scope; - setUp(() => scope = new Scope.identity()); + 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); + }); + }); + + group('Deduplicating scope', () { + Scope scope; + + setUp(() => scope = new Scope.dedupe()); test('should just output non-prefixed and de-duplicate imports', () { var identifiers = [ From ae6d8c4c6d4386d0a57c1033193230c13423ed70 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Sep 2016 17:16:23 -0700 Subject: [PATCH 4/5] Add some support to LibraryBuilder and a test. --- lib/src/builders/file_builder.dart | 7 ++++++ test/integration_test.dart | 36 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/src/builders/file_builder.dart b/lib/src/builders/file_builder.dart index 1d4d8d3..d6fdcc7 100644 --- a/lib/src/builders/file_builder.dart +++ b/lib/src/builders/file_builder.dart @@ -77,6 +77,13 @@ class LibraryBuilder extends FileBuilder { void addDirective(CodeBuilder directive) { _astNode.directives.add(directive.toAst()); } + + @override + CompilationUnit toAst() { + var originalAst = super.toAst(); + originalAst.directives..addAll(_scope.getImports().map((i) => i.toAst())); + return originalAst; + } } /// Builds a `part of` [CompilationUnit] AST for an existing Dart library. diff --git a/test/integration_test.dart b/test/integration_test.dart index 8510c7b..4571d66 100644 --- a/test/integration_test.dart +++ b/test/integration_test.dart @@ -55,4 +55,40 @@ void main() { ), ); }); + + 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) {} + ''', + ), + ); + }); } From 6834d1270f0de08066fbc4fb4f687e8ce59f8d04 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Thu, 15 Sep 2016 17:37:58 -0700 Subject: [PATCH 5/5] Pubspec +1 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 77cd407..d3f7b32 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: code_builder -version: 0.1.0-dev+1 +version: 0.1.0-dev+2 description: A fluent API for generating Dart code author: Dart Team homepage: https://github.com/dart-lang/code_builder