From 9e1158de4fc7562b550f0929b63ed1dfbaa02a17 Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Tue, 7 Jul 2015 09:03:11 -0700 Subject: [PATCH] fix(transformer): Support prefixed annotations in the transformer. closes https://github.com/angular/angular/issues/2754 --- .../transform/common/annotation_matcher.dart | 30 ++++- .../view_definition_creator.dart | 9 +- .../common/annotation_matcher_test.dart | 104 ++++++++++++++++++ .../template_compiler/all_tests.dart | 12 ++ .../expected/ng2_prefix.ng_deps.dart | 22 ++++ .../with_prefix_files/ng2_prefix.ng_deps.dart | 20 ++++ .../with_prefix_files/ng2_prefix.ng_meta.json | 12 ++ 7 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 modules/angular2/test/transform/common/annotation_matcher_test.dart create mode 100644 modules/angular2/test/transform/template_compiler/with_prefix_files/expected/ng2_prefix.ng_deps.dart create mode 100644 modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_deps.dart create mode 100644 modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json diff --git a/modules/angular2/src/transform/common/annotation_matcher.dart b/modules/angular2/src/transform/common/annotation_matcher.dart index dd8b6b2132..115beb2db2 100644 --- a/modules/angular2/src/transform/common/annotation_matcher.dart +++ b/modules/angular2/src/transform/common/annotation_matcher.dart @@ -127,17 +127,37 @@ class AnnotationMatcher { // Checks if an [Annotation] matches an [AnnotationDescriptor]. static bool _matchAnnotation( Annotation annotation, AnnotationDescriptor descriptor, AssetId assetId) { - if (annotation.name.name != descriptor.name) return false; + String name; + Identifier prefix; + if (annotation.name is PrefixedIdentifier) { + // TODO(jakemac): Shouldn't really need a cast here, remove once + // https://github.com/dart-lang/sdk/issues/23798 is fixed. + var prefixedName = annotation.name as PrefixedIdentifier; + name = prefixedName.identifier.name; + prefix = prefixedName.prefix; + } else { + name = annotation.name.name; + } + if (name != descriptor.name) return false; return (annotation.root as CompilationUnit).directives .where((d) => d is ImportDirective) .any((ImportDirective i) { + var importMatch = false; var uriString = i.uri.stringValue; - if (uriString == descriptor.import) return true; - if (uriString.startsWith('package:') || uriString.startsWith('dart:')) { + if (uriString == descriptor.import) { + importMatch = true; + } else if (uriString.startsWith('package:') || + uriString.startsWith('dart:')) { return false; + } else { + importMatch = descriptor.assetId == + uriToAssetId(assetId, uriString, logger, null); } - return descriptor.assetId == - uriToAssetId(assetId, uriString, logger, null); + + if (!importMatch) return false; + if (prefix == null) return i.prefix == null; + if (i.prefix == null) return false; + return prefix.name == i.prefix.name; }); } } diff --git a/modules/angular2/src/transform/template_compiler/view_definition_creator.dart b/modules/angular2/src/transform/template_compiler/view_definition_creator.dart index 701dd06d75..a6c8932934 100644 --- a/modules/angular2/src/transform/template_compiler/view_definition_creator.dart +++ b/modules/angular2/src/transform/template_compiler/view_definition_creator.dart @@ -36,8 +36,13 @@ class ViewDefinitionEntry { String _getComponentId(AssetId assetId, String className) => '$className'; // TODO(kegluenq): Improve this test. -bool _isViewAnnotation(InstanceCreationExpression node) => - '${node.constructorName.type}' == 'View'; +bool _isViewAnnotation(InstanceCreationExpression node) { + var constructorName = node.constructorName.type.name; + if (constructorName is PrefixedIdentifier) { + constructorName = constructorName.identifier; + } + return constructorName.name == 'View'; +} /// Creates [ViewDefinition] objects for all `View` `Directive`s defined in /// `entryPoint`. diff --git a/modules/angular2/test/transform/common/annotation_matcher_test.dart b/modules/angular2/test/transform/common/annotation_matcher_test.dart new file mode 100644 index 0000000000..2bd0d17cd4 --- /dev/null +++ b/modules/angular2/test/transform/common/annotation_matcher_test.dart @@ -0,0 +1,104 @@ +library angular2.test.transform.common.annotation_matcher_test; + +import 'dart:async'; +import 'package:analyzer/analyzer.dart'; +import 'package:angular2/src/transform/common/annotation_matcher.dart'; +import 'package:barback/barback.dart' show AssetId; +import 'package:guinness/guinness.dart'; + +main() { + allTests(); +} + +var simpleAst = parseCompilationUnit(''' +import 'package:test/test.dart'; + +@Test() +var foo; +'''); + +var namespacedAst = parseCompilationUnit(''' +import 'package:test/test.dart' as test; + +@test.Test() +var foo; +'''); + +var relativePathAst = parseCompilationUnit(''' +import 'test.dart'; + +@Test() +var foo; +'''); + +var namespacedRelativePathAst = parseCompilationUnit(''' +import 'test.dart' as test; + +@test.Test() +var foo; +'''); + +void allTests() { + it('should be able to match basic annotations.', () { + var matcher = new AnnotationMatcher() + ..add(const AnnotationDescriptor('Test', 'package:test/test.dart', null)); + var visitor = new MatchRecordingVisitor(matcher); + simpleAst.accept(visitor); + expect(visitor.matches.length).toBe(1); + }); + + it('should be able to match namespaced annotations.', () { + var matcher = new AnnotationMatcher() + ..add(const AnnotationDescriptor('Test', 'package:test/test.dart', null)); + var visitor = new MatchRecordingVisitor(matcher); + namespacedAst.accept(visitor); + expect(visitor.matches.length).toBe(1); + }); + + it('should be able to match relative imports.', () { + var matcher = new AnnotationMatcher() + ..add(const AnnotationDescriptor('Test', 'package:test/test.dart', null)); + var visitor = + new MatchRecordingVisitor(matcher, new AssetId('test', 'lib/foo.dart')); + relativePathAst.accept(visitor); + expect(visitor.matches.length).toBe(1); + }); + + it('should be able to match relative imports with a namespace.', () { + var matcher = new AnnotationMatcher() + ..add(const AnnotationDescriptor('Test', 'package:test/test.dart', null)); + var visitor = + new MatchRecordingVisitor(matcher, new AssetId('test', 'lib/foo.dart')); + namespacedRelativePathAst.accept(visitor); + expect(visitor.matches.length).toBe(1); + }); + + it('should not match annotations if the import is missing.', () { + var matcher = new AnnotationMatcher() + ..add(const AnnotationDescriptor('Test', 'package:test/foo.dart', null)); + var visitor = new MatchRecordingVisitor(matcher); + simpleAst.accept(visitor); + expect(visitor.matches.isEmpty).toBeTrue(); + }); + + it('should not match annotations if the name is different.', () { + var matcher = new AnnotationMatcher() + ..add(const AnnotationDescriptor('Foo', 'package:test/test.dart', null)); + var visitor = new MatchRecordingVisitor(matcher); + simpleAst.accept(visitor); + expect(visitor.matches.isEmpty).toBeTrue(); + }); +} + +class MatchRecordingVisitor extends RecursiveAstVisitor { + final AssetId assetId; + final AnnotationMatcher matcher; + final List matches = []; + + MatchRecordingVisitor(this.matcher, [this.assetId]) : super(); + + @override + void visitAnnotation(Annotation annotation) { + if (matcher.hasMatch(annotation, assetId)) matches.add(annotation); + } +} diff --git a/modules/angular2/test/transform/template_compiler/all_tests.dart b/modules/angular2/test/transform/template_compiler/all_tests.dart index 9516222516..af3d3e155a 100644 --- a/modules/angular2/test/transform/template_compiler/all_tests.dart +++ b/modules/angular2/test/transform/template_compiler/all_tests.dart @@ -13,6 +13,8 @@ import '../common/read_file.dart'; var formatter = new DartFormatter(); +main() => allTests(); + void allTests() { Html5LibDomAdapter.makeCurrent(); AssetReader reader = new TestAssetReader(); @@ -93,6 +95,16 @@ void allTests() { _formatThenExpectEquals(output, expected); }); + it('should parse angular directives with a prefix', () async { + var inputPath = + 'template_compiler/with_prefix_files/ng2_prefix.ng_deps.dart'; + var expected = readFile( + 'template_compiler/with_prefix_files/expected/ng2_prefix.ng_deps.dart'); + + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); + it('should create the same output for multiple calls.', () async { var inputPath = 'template_compiler/inline_expression_files/hello.ng_deps.dart'; diff --git a/modules/angular2/test/transform/template_compiler/with_prefix_files/expected/ng2_prefix.ng_deps.dart b/modules/angular2/test/transform/template_compiler/with_prefix_files/expected/ng2_prefix.ng_deps.dart new file mode 100644 index 0000000000..bfa02da9be --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/with_prefix_files/expected/ng2_prefix.ng_deps.dart @@ -0,0 +1,22 @@ +library angular2.test.transform.template_compiler.with_prefix_files.ng2_prefix.ng_deps.dart; + +import 'ng2_prefix.dart'; +import 'package:angular2/angular2.dart' as ng2 + show bootstrap, Component, Directive, View, NgElement; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(MyApp, { + 'factory': () => new MyApp(), + 'parameters': const [const []], + 'annotations': const [ + const ng2.Component(selector: 'my-app'), + const ng2.View(template: 'MyApp {{name}}') + ] + }) + ..registerGetters({'name': (o) => o.name}) + ..registerSetters({'name': (o, v) => o.name = v}); +} diff --git a/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_deps.dart b/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_deps.dart new file mode 100644 index 0000000000..2fc86c0132 --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_deps.dart @@ -0,0 +1,20 @@ +library angular2.test.transform.template_compiler.with_prefix_files.ng2_prefix.ng_deps.dart; + +import 'ng2_prefix.dart'; +import 'package:angular2/angular2.dart' as ng2 + show bootstrap, Component, Directive, View, NgElement; + +var _visited = false; +void initReflector(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(MyApp, { + 'factory': () => new MyApp(), + 'parameters': const [const []], + 'annotations': const [ + const ng2.Component(selector: 'my-app'), + const ng2.View(template: 'MyApp {{name}}') + ] + }); +} diff --git a/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json b/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json new file mode 100644 index 0000000000..7766355a52 --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/with_prefix_files/ng2_prefix.ng_meta.json @@ -0,0 +1,12 @@ +{ + "MyApp":{ + "id":"MyApp", + "selector":"my-app", + "compileChildren":true, + "host":{}, + "properties":[], + "readAttributes":[], + "type":1, + "version":1 + } +}