From aa480fee72d3a501cbdff9aece9b6aa96820ffc2 Mon Sep 17 00:00:00 2001 From: Tim Blasi Date: Tue, 4 Aug 2015 16:50:31 -0700 Subject: [PATCH] feat(dart/transform): Support `part` directives Allow users to split libraries using the `part` directive. Closes #1817 --- .../directive_lifecycle_reflector.dart | 2 + modules/angular2/src/test_lib/test_lib.dart | 6 +- .../transform/common/async_string_writer.dart | 2 +- .../directive_processor/rewriter.dart | 338 ++++++++++++------ .../src/web-workers/ui/event_serializer.dart | 3 +- .../directive_processor/all_tests.dart | 14 +- .../expected/main.ng_deps.dart | 25 ++ .../multiple_part_files/main.dart | 11 + .../multiple_part_files/part1.dart | 6 + .../multiple_part_files/part2.dart | 6 + .../part_files/expected/main.ng_deps.dart | 21 ++ .../directive_processor/part_files/main.dart | 10 + .../directive_processor/part_files/part.dart | 6 + 13 files changed, 335 insertions(+), 115 deletions(-) create mode 100644 modules/angular2/test/transform/directive_processor/multiple_part_files/expected/main.ng_deps.dart create mode 100644 modules/angular2/test/transform/directive_processor/multiple_part_files/main.dart create mode 100644 modules/angular2/test/transform/directive_processor/multiple_part_files/part1.dart create mode 100644 modules/angular2/test/transform/directive_processor/multiple_part_files/part2.dart create mode 100644 modules/angular2/test/transform/directive_processor/part_files/expected/main.ng_deps.dart create mode 100644 modules/angular2/test/transform/directive_processor/part_files/main.dart create mode 100644 modules/angular2/test/transform/directive_processor/part_files/part.dart diff --git a/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart b/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart index e3a558060b..76e3ea61e3 100644 --- a/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart +++ b/modules/angular2/src/core/compiler/directive_lifecycle_reflector.dart @@ -1,3 +1,5 @@ +library angular2.src.core.compiler.directive_lifecycle_reflector; + import 'package:angular2/src/core/annotations_impl/annotations.dart'; import 'package:angular2/src/core/compiler/interfaces.dart'; import 'package:angular2/src/reflection/reflection.dart'; diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart index 812100ed13..fa101c2928 100644 --- a/modules/angular2/src/test_lib/test_lib.dart +++ b/modules/angular2/src/test_lib/test_lib.dart @@ -100,7 +100,8 @@ class Expect extends gns.Expect { void toThrowError([message = ""]) => toThrowWith(message: message); void toThrowErrorWith(message) => expectException(this.actual, message); void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future); - void toHaveCssClass(className) => gns.guinness.matchers.toBeTrue(DOM.hasClass(actual, className)); + void toHaveCssClass(className) => + gns.guinness.matchers.toBeTrue(DOM.hasClass(actual, className)); void toImplement(expected) => toBeA(expected); void toBeNaN() => gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0); @@ -139,7 +140,8 @@ class NotExpect extends gns.NotExpect { void toEqual(expected) => toHaveSameProps(expected); void toBePromise() => gns.guinness.matchers.toBeFalse(actual is Future); - void toHaveCssClass(className) => gns.guinness.matchers.toBeFalse(DOM.hasClass(actual, className)); + void toHaveCssClass(className) => + gns.guinness.matchers.toBeFalse(DOM.hasClass(actual, className)); void toBeNull() => gns.guinness.matchers.toBeFalse(actual == null); Function get _expect => gns.guinness.matchers.expect; } diff --git a/modules/angular2/src/transform/common/async_string_writer.dart b/modules/angular2/src/transform/common/async_string_writer.dart index 14d562de55..a54ff7205e 100644 --- a/modules/angular2/src/transform/common/async_string_writer.dart +++ b/modules/angular2/src/transform/common/async_string_writer.dart @@ -16,7 +16,7 @@ class AsyncStringWriter extends PrintWriter { : _curr = curr, _bufs = [curr]; - AsyncStringWriter() : this._(new StringBuffer()); + AsyncStringWriter([Object content = ""]) : this._(new StringBuffer(content)); void print(x) { _curr.write(x); diff --git a/modules/angular2/src/transform/directive_processor/rewriter.dart b/modules/angular2/src/transform/directive_processor/rewriter.dart index 82c4175a1b..422fcf4afc 100644 --- a/modules/angular2/src/transform/directive_processor/rewriter.dart +++ b/modules/angular2/src/transform/directive_processor/rewriter.dart @@ -3,6 +3,7 @@ library angular2.transform.directive_processor.rewriter; import 'dart:async'; import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/src/generated/java_core.dart'; import 'package:angular2/src/render/xhr.dart' show XHR; import 'package:angular2/src/transform/common/annotation_matcher.dart'; import 'package:angular2/src/transform/common/asset_reader.dart'; @@ -13,7 +14,9 @@ import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/xhr_impl.dart'; import 'package:angular2/src/transform/common/ng_meta.dart'; import 'package:barback/barback.dart' show AssetId; +import 'package:code_transformers/assets.dart'; import 'package:path/path.dart' as path; +import 'package:source_span/source_span.dart'; import 'visitors.dart'; @@ -29,33 +32,233 @@ Future createNgDeps(AssetReader reader, AssetId assetId, {bool inlineViews}) async { // TODO(kegluneq): Shortcut if we can determine that there are no // [Directive]s present, taking into account `export`s. + var code = await reader.readAsString(assetId); + + var directivesVisitor = new _NgDepsDirectivesVisitor(); + parseDirectives(code, name: assetId.path) + .directives + .accept(directivesVisitor); + + // If this is part of another library, its contents will be processed by its + // parent, so it does not need its own `.ng_deps.dart` file. + if (directivesVisitor.isPart) return null; + var writer = new AsyncStringWriter(); - var visitor = new CreateNgDepsVisitor( - writer, + directivesVisitor.writeTo(writer, assetId); + + writer + ..println('var _visited = false;') + ..println('void ${SETUP_METHOD_NAME}() {') + ..println('if (_visited) return; _visited = true;'); + + var declarationsCode = + await _getAllDeclarations(reader, assetId, code, directivesVisitor); + var declarationsVisitor = new _NgDepsDeclarationsVisitor( assetId, + writer, new XhrImpl(reader, assetId), annotationMatcher, _interfaceMatcher, ngMeta, inlineViews: inlineViews); - var code = await reader.readAsString(assetId); - parseCompilationUnit(code, name: assetId.path).accept(visitor); + parseCompilationUnit(declarationsCode, name: '${assetId.path} and parts') + .declarations + .accept(declarationsVisitor); + if (declarationsVisitor.shouldCreateNgDeps) { + writer.println(';'); + } + writer.println('}'); - // If this library does not define an `@Injectable` and it does not import - // any libaries that could, then we do not need to generate a `.ng_deps - // .dart` file for it. - if (!visitor._foundNgInjectable && !visitor._usesNonLangLibs) return null; + if (!directivesVisitor.shouldCreateNgDeps && + !declarationsVisitor.shouldCreateNgDeps) return null; - return await writer.asyncToString(); + return writer.asyncToString(); } InterfaceMatcher _interfaceMatcher = new InterfaceMatcher(); -/// Visitor responsible for processing [CompilationUnit] and creating an -/// associated .ng_deps.dart file. -class CreateNgDepsVisitor extends Object with SimpleAstVisitor { +/// Processes `visitor.parts`, reading and appending their contents to the +/// original `code`. +/// Order of `part`s is preserved. That is, if in the main library we have +/// ``` +/// library main; +/// +/// part 'lib1.dart' +/// part 'lib2.dart' +/// ``` +/// The output will first have the entirety of the original file, followed by +/// the contents of lib1.dart followed by the contents of lib2.dart. +Future _getAllDeclarations(AssetReader reader, AssetId assetId, + String code, _NgDepsDirectivesVisitor visitor) { + if (visitor.parts.isEmpty) return new Future.value(code); + + var partsStart = visitor.parts.first.offset, + partsEnd = visitor.parts.last.end; + + var asyncWriter = new AsyncStringWriter(code.substring(0, partsStart)); + visitor.parts.forEach((partDirective) { + var uri = stringLiteralToString(partDirective.uri); + var partAssetId = uriToAssetId(assetId, uri, logger, null /* span */, + errorOnAbsolute: false); + asyncWriter.asyncPrint(reader.readAsString(partAssetId).then((partCode) { + if (partCode == null || partCode.isEmpty) { + logger.warning('Empty part at "${partDirective.uri}. Ignoring.', + asset: partAssetId); + return ''; + } + // Remove any directives -- we just want declarations. + var parsedDirectives = parseDirectives(partCode, name: uri).directives; + return partCode.substring(parsedDirectives.last.end); + }).catchError((err, stackTrace) { + logger.warning( + 'Failed while reading part at ${partDirective.uri}. Ignoring.\n' + 'Error: $err\n' + 'Stack Trace: $stackTrace', + asset: partAssetId, + span: new SourceFile(code, url: path.basename(assetId.path)) + .span(partDirective.offset, partDirective.end)); + })); + }); + asyncWriter.print(code.substring(partsEnd)); + + return asyncWriter.asyncToString(); +} + +/// Visitor responsible for flattening directives passed to it. +/// Once this has visited an Ast, use [#writeTo] to write out the directives +/// for the .ng_deps.dart file. See [#writeTo] for details. +class _NgDepsDirectivesVisitor extends Object with SimpleAstVisitor { + /// Whether this library `imports` or `exports` any non-'dart:' libraries. + bool _usesNonLangLibs = false; + + /// Whether the file we are processing is a part, that is, whether we have + /// visited a `part of` directive. + bool _isPart = false; + + // TODO(kegluneq): Support an intermediate representation of NgDeps and use it + // instead of storing generated code. + LibraryDirective _library = null; + ScriptTag _scriptTag = null; + final List _importAndExports = []; + final List _parts = []; + + bool get shouldCreateNgDeps { + // If this library does not define an `@Injectable` and it does not import + // any libaries that could, then we do not need to generate a `.ng_deps + // .dart` file for it. + if (!_usesNonLangLibs) return false; + if (_isPart) return false; + + return true; + } + + bool get usesNonLangLibs => _usesNonLangLibs; + bool get isPart => _isPart; + + /// In the order encountered in the source. + Iterable get parts => _parts; + + @override + Object visitScriptTag(ScriptTag node) { + _scriptTag = node; + return null; + } + + @override + Object visitCompilationUnit(CompilationUnit node) { + node.directives.accept(this); + return null; + } + + void _updateUsesNonLangLibs(UriBasedDirective directive) { + _usesNonLangLibs = _usesNonLangLibs || + !stringLiteralToString(directive.uri).startsWith('dart:'); + } + + @override + Object visitImportDirective(ImportDirective node) { + _updateUsesNonLangLibs(node); + _importAndExports.add(node); + return null; + } + + @override + Object visitExportDirective(ExportDirective node) { + _updateUsesNonLangLibs(node); + _importAndExports.add(node); + return null; + } + + @override + Object visitLibraryDirective(LibraryDirective node) { + if (node != null) { + _library = node; + } + return null; + } + + @override + Object visitPartDirective(PartDirective node) { + _parts.add(node); + return null; + } + + @override + Object visitPartOfDirective(PartOfDirective node) { + _isPart = true; + return null; + } + + /// Write the directives for the .ng_deps.dart for `processedFile` to + /// `writer`. The .ng_deps.dart file has the same directives as + /// `processedFile` with some exceptions (mentioned below). + void writeTo(PrintWriter writer, AssetId processedFile) { + var copyVisitor = new ToSourceVisitor(writer); + + if (_scriptTag != null) { + _scriptTag.accept(copyVisitor); + writer.newLine(); + } + + if (_library != null && _library.name != null) { + writer.print('library '); + _library.name.accept(copyVisitor); + writer.println('$DEPS_EXTENSION;'); + } + + // We do not output [PartDirective]s, which would not be valid now that we + // have changed the library. + + // We need to import & export the original file. + var origDartFile = path.basename(processedFile.path); + writer.println('''import '$origDartFile';'''); + writer.println('''export '$origDartFile';'''); + + // Used to register reflective information. + writer.println("import '$_REFLECTOR_IMPORT' as $_REF_PREFIX;"); + + _importAndExports.forEach((node) { + if (node.isSynthetic) return; + + // Ignore deferred imports here so as to not load the deferred libraries + // code in the current library causing much of the code to not be + // deferred. Instead `DeferredRewriter` will rewrite the code as to load + // `ng_deps` in a deferred way. + if (node is ImportDirective && node.deferredKeyword != null) return; + + node.accept(copyVisitor); + }); + } +} + +/// Visitor responsible for visiting a file's [Declaration]s and outputting the +/// code necessary to register the file with the Angular 2 system. +class _NgDepsDeclarationsVisitor extends Object with SimpleAstVisitor { final AsyncStringWriter writer; + /// The file we are processing. + final AssetId assetId; + /// Output ngMeta information about aliases. // TODO(sigmund): add more to ngMeta. Currently this only contains aliasing // information, but we could produce here all the metadata we need and avoid @@ -65,25 +268,26 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor { /// Whether an Angular 2 `Injectable` has been found. bool _foundNgInjectable = false; - /// Whether this library `imports` or `exports` any non-'dart:' libraries. - bool _usesNonLangLibs = false; - - /// Whether we have written an import of base file - /// (the file we are processing). - bool _wroteBaseLibImport = false; + /// Visitor that writes out code for AstNodes visited. final ToSourceVisitor _copyVisitor; final FactoryTransformVisitor _factoryVisitor; final ParameterTransformVisitor _paramsVisitor; final AnnotationsTransformVisitor _metaVisitor; + + /// Responsible for testing whether [Annotation]s are those recognized by + /// Angular 2, for example `@Component`. final AnnotationMatcher _annotationMatcher; + + /// Responsible for testing whether interfaces are recognized by Angular2, + /// for example `OnChange`. final InterfaceMatcher _interfaceMatcher; - /// The assetId for the file which we are parsing. - final AssetId assetId; + /// Used to fetch linked files. + final XHR _xhr; - CreateNgDepsVisitor( - AsyncStringWriter writer, + _NgDepsDeclarationsVisitor( AssetId assetId, + AsyncStringWriter writer, XHR xhr, AnnotationMatcher annotationMatcher, InterfaceMatcher interfaceMatcher, @@ -98,75 +302,10 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor { inlineViews: inlineViews), _annotationMatcher = annotationMatcher, _interfaceMatcher = interfaceMatcher, - this.assetId = assetId; + this.assetId = assetId, + _xhr = xhr; - void _visitNodeListWithSeparator(NodeList list, String separator) { - if (list == null) return; - for (var i = 0, iLen = list.length; i < iLen; ++i) { - if (i != 0) { - writer.print(separator); - } - list[i].accept(this); - } - } - - @override - Object visitCompilationUnit(CompilationUnit node) { - _visitNodeListWithSeparator(node.directives, " "); - _openFunctionWrapper(); - _visitNodeListWithSeparator(node.declarations, " "); - _closeFunctionWrapper(); - return null; - } - - /// Write the import to the file the .ng_deps.dart file is based on if it - /// has not yet been written. - void _maybeWriteImport() { - if (_wroteBaseLibImport) return; - _wroteBaseLibImport = true; - var origDartFile = path.basename(assetId.path); - writer.print('''import '$origDartFile';'''); - writer.print('''export '$origDartFile';'''); - writer.print("import '$_REFLECTOR_IMPORT' as $_REF_PREFIX;"); - } - - void _updateUsesNonLangLibs(UriBasedDirective directive) { - _usesNonLangLibs = _usesNonLangLibs || - !stringLiteralToString(directive.uri).startsWith('dart:'); - } - - @override - Object visitImportDirective(ImportDirective node) { - _maybeWriteImport(); - _updateUsesNonLangLibs(node); - // Ignore deferred imports here so as to not load the deferred libraries - // code in the current library causing much of the code to not be - // deferred. Instead `DeferredRewriter` will rewrite the code as to load - // `ng_deps` in a deferred way. - if (node.deferredKeyword != null) return null; - return node.accept(_copyVisitor); - } - - @override - Object visitExportDirective(ExportDirective node) { - _maybeWriteImport(); - _updateUsesNonLangLibs(node); - return node.accept(_copyVisitor); - } - - void _openFunctionWrapper() { - _maybeWriteImport(); - writer.print('var _visited = false;' - 'void ${SETUP_METHOD_NAME}() {' - 'if (_visited) return; _visited = true;'); - } - - void _closeFunctionWrapper() { - if (_foundNgInjectable) { - writer.print(';'); - } - writer.print('}'); - } + bool get shouldCreateNgDeps => _foundNgInjectable; ConstructorDeclaration _getCtor(ClassDeclaration node) { int numCtorsFound = 0; @@ -271,25 +410,6 @@ class CreateNgDepsVisitor extends Object with SimpleAstVisitor { return node.accept(_copyVisitor); } - @override - Object visitLibraryDirective(LibraryDirective node) { - if (node != null && node.name != null) { - writer.print('library '); - _nodeToSource(node.name); - writer.print('$DEPS_EXTENSION;'); - } - return null; - } - - @override - Object visitPartOfDirective(PartOfDirective node) { - // TODO(kegluneq): Consider importing [node.libraryName]. - logger.warning('[${assetId}]: ' - 'Found `part of` directive while generating ${DEPS_EXTENSION} file, ' - 'Transform may fail due to missing imports in generated file.'); - return null; - } - @override Object visitPrefixedIdentifier(PrefixedIdentifier node) => _nodeToSource(node); diff --git a/modules/angular2/src/web-workers/ui/event_serializer.dart b/modules/angular2/src/web-workers/ui/event_serializer.dart index 78d9c5783f..2c4d06ef7f 100644 --- a/modules/angular2/src/web-workers/ui/event_serializer.dart +++ b/modules/angular2/src/web-workers/ui/event_serializer.dart @@ -95,7 +95,8 @@ Map serializeKeyboardEvent(dynamic e) { } // TODO(jteplitz602): #3374. See above. -Map addTarget(dynamic e, Map serializedEvent) { +Map addTarget( + dynamic e, Map serializedEvent) { if (NODES_WITH_VALUE.contains(e.target.tagName.toLowerCase())) { serializedEvent['target'] = {'value': e.target.value}; if (e.target is InputElement) { diff --git a/modules/angular2/test/transform/directive_processor/all_tests.dart b/modules/angular2/test/transform/directive_processor/all_tests.dart index 77829d20f4..aa6a436094 100644 --- a/modules/angular2/test/transform/directive_processor/all_tests.dart +++ b/modules/angular2/test/transform/directive_processor/all_tests.dart @@ -12,6 +12,7 @@ import 'package:code_transformers/messages/build_logger.dart'; import 'package:dart_style/dart_style.dart'; import 'package:guinness/guinness.dart'; import 'package:path/path.dart' as path; +import 'package:source_span/source_span.dart'; import '../common/read_file.dart'; var formatter = new DartFormatter(); @@ -24,6 +25,14 @@ void allTests() { _testProcessor('should preserve parameter annotations as const instances.', 'parameter_metadata/soup.dart'); + _testProcessor('should handle `part` directives.', 'part_files/main.dart'); + + _testProcessor('should handle multiple `part` directives.', + 'multiple_part_files/main.dart'); + + _testProcessor('should not generate .ng_deps.dart for `part` files.', + 'part_files/part.dart'); + _testProcessor('should recognize custom annotations with package: imports', 'custom_metadata/package_soup.dart', customDescriptors: [ @@ -166,8 +175,9 @@ void _testProcessor(String name, String inputPath, if (output == null) { expect(await reader.hasInput(expectedNgDepsId)).toBeFalse(); } else { - var input = await reader.readAsString(expectedNgDepsId); - expect(formatter.format(output)).toEqual(formatter.format(input)); + var expectedOutput = await reader.readAsString(expectedNgDepsId); + expect(formatter.format(output)) + .toEqual(formatter.format(expectedOutput)); } if (ngMeta.isEmpty) { expect(await reader.hasInput(expectedAliasesId)).toBeFalse(); diff --git a/modules/angular2/test/transform/directive_processor/multiple_part_files/expected/main.ng_deps.dart b/modules/angular2/test/transform/directive_processor/multiple_part_files/expected/main.ng_deps.dart new file mode 100644 index 0000000000..3e96886458 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/multiple_part_files/expected/main.ng_deps.dart @@ -0,0 +1,25 @@ +library main.ng_deps.dart; + +import 'main.dart'; +export 'main.dart'; +import 'package:angular2/src/reflection/reflection.dart' as _ngRef; +import 'package:angular2/src/core/annotations_impl/annotations.dart'; + +var _visited = false; +void initReflector() { + if (_visited) return; + _visited = true; + _ngRef.reflector + ..registerType( + Part1Component, + new _ngRef.ReflectionInfo(const [const Component(selector: '[part1]')], + const [], () => new Part1Component())) + ..registerType( + Part2Component, + new _ngRef.ReflectionInfo(const [const Component(selector: '[part2]')], + const [], () => new Part2Component())) + ..registerType( + MainComponent, + new _ngRef.ReflectionInfo(const [const Component(selector: '[main]')], + const [], () => new MainComponent())); +} diff --git a/modules/angular2/test/transform/directive_processor/multiple_part_files/main.dart b/modules/angular2/test/transform/directive_processor/multiple_part_files/main.dart new file mode 100644 index 0000000000..613c58d8f9 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/multiple_part_files/main.dart @@ -0,0 +1,11 @@ +library main; + +import 'package:angular2/src/core/annotations_impl/annotations.dart'; + +part 'part1.dart'; +part 'part2.dart'; + +@Component(selector: '[main]') +class MainComponent { + MainComponent(); +} diff --git a/modules/angular2/test/transform/directive_processor/multiple_part_files/part1.dart b/modules/angular2/test/transform/directive_processor/multiple_part_files/part1.dart new file mode 100644 index 0000000000..d3a65721a0 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/multiple_part_files/part1.dart @@ -0,0 +1,6 @@ +part of main; + +@Component(selector: '[part1]') +class Part1Component { + Part1Component(); +} diff --git a/modules/angular2/test/transform/directive_processor/multiple_part_files/part2.dart b/modules/angular2/test/transform/directive_processor/multiple_part_files/part2.dart new file mode 100644 index 0000000000..a0c9289424 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/multiple_part_files/part2.dart @@ -0,0 +1,6 @@ +part of main; + +@Component(selector: '[part2]') +class Part2Component { + Part2Component(); +} diff --git a/modules/angular2/test/transform/directive_processor/part_files/expected/main.ng_deps.dart b/modules/angular2/test/transform/directive_processor/part_files/expected/main.ng_deps.dart new file mode 100644 index 0000000000..efe47617cf --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/part_files/expected/main.ng_deps.dart @@ -0,0 +1,21 @@ +library main.ng_deps.dart; + +import 'main.dart'; +export 'main.dart'; +import 'package:angular2/src/reflection/reflection.dart' as _ngRef; +import 'package:angular2/src/core/annotations_impl/annotations.dart'; + +var _visited = false; +void initReflector() { + if (_visited) return; + _visited = true; + _ngRef.reflector + ..registerType( + PartComponent, + new _ngRef.ReflectionInfo(const [const Component(selector: '[part]')], + const [], () => new PartComponent())) + ..registerType( + MainComponent, + new _ngRef.ReflectionInfo(const [const Component(selector: '[main]')], + const [], () => new MainComponent())); +} diff --git a/modules/angular2/test/transform/directive_processor/part_files/main.dart b/modules/angular2/test/transform/directive_processor/part_files/main.dart new file mode 100644 index 0000000000..a8d23a7317 --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/part_files/main.dart @@ -0,0 +1,10 @@ +library main; + +import 'package:angular2/src/core/annotations_impl/annotations.dart'; + +part 'part.dart'; + +@Component(selector: '[main]') +class MainComponent { + MainComponent(); +} diff --git a/modules/angular2/test/transform/directive_processor/part_files/part.dart b/modules/angular2/test/transform/directive_processor/part_files/part.dart new file mode 100644 index 0000000000..4c04f4969a --- /dev/null +++ b/modules/angular2/test/transform/directive_processor/part_files/part.dart @@ -0,0 +1,6 @@ +part of main; + +@Component(selector: '[part]') +class PartComponent { + PartComponent(); +}