Skip to content

Commit f4e0f51

Browse files
author
Tim Blasi
committed
feat(dart/transform) Register parameter metadata information
Adds any metadata attached to a parameter to the "parameters" value passed in to `registerType`. For example: `MyComponent(@Inject(Foo) foo)` generates `"parameters": const [const [const Inject(Foo)]]` Also reorganizes the testing code. Closes angular#7
1 parent e1a1dd0 commit f4e0f51

File tree

41 files changed

+271
-167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+271
-167
lines changed

modules/angular2/src/transform/bind_generator/generator.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class CreateNgSettersVisitor extends ToSourceVisitor with VisitorMixin {
2727

2828
@override
2929
Object visitMethodInvocation(MethodInvocation node) {
30-
var isRegisterType = node.methodName.toString() == REGISTER_TYPE_METHOD_NAME;
30+
var isRegisterType =
31+
node.methodName.toString() == REGISTER_TYPE_METHOD_NAME;
3132
// The first argument to a `registerType` call is the type.
3233
extractVisitor.currentName = node.argumentList.arguments[0] is Identifier
3334
? node.argumentList.arguments[0]

modules/angular2/src/transform/common/formatter.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:dart_style/dart_style.dart';
44

55
import 'logging.dart';
66

7-
DartFormatter _formatter;
7+
DartFormatter _formatter = null;
88

99
void init(DartFormatter formatter) {
1010
if (_formatter != null) {

modules/angular2/src/transform/directive_processor/visitors.dart

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:angular2/src/transform/common/visitor_mixin.dart';
77

88
/// SourceVisitor designed to accept [ConstructorDeclaration] nodes.
99
class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
10+
bool _withParameterAnnotations = true;
1011
bool _withParameterTypes = true;
1112
bool _withParameterNames = true;
1213
final PrintWriter writer;
@@ -21,7 +22,13 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
2122

2223
/// If [_withParameterTypes] is true, this method outputs [node]'s type. If
2324
/// [_withParameterNames] is true, this method outputs [node]'s identifier.
24-
Object _visitNormalFormalParameter(TypeName type, SimpleIdentifier name) {
25+
Object _visitNormalFormalParameter(
26+
NodeList<Annotation> metadata, TypeName type, SimpleIdentifier name) {
27+
if (_withParameterAnnotations && metadata != null) {
28+
assert(_withParameterTypes);
29+
var suffix = type != null ? ', ' : '';
30+
visitNodeListWithSeparatorAndSuffix(metadata, ', ', suffix);
31+
}
2532
if (_withParameterTypes) {
2633
visitNodeWithSuffix(type, ' ');
2734
}
@@ -48,7 +55,8 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
4855

4956
@override
5057
Object visitSimpleFormalParameter(SimpleFormalParameter node) {
51-
return _visitNormalFormalParameter(node.type, node.identifier);
58+
return _visitNormalFormalParameter(
59+
node.metadata, node.type, node.identifier);
5260
}
5361

5462
@override
@@ -61,14 +69,14 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
6169
if (type == null) {
6270
type = _fieldNameToType[node.identifier.toString()];
6371
}
64-
return _visitNormalFormalParameter(type, node.identifier);
72+
return _visitNormalFormalParameter(node.metadata, type, node.identifier);
6573
}
6674

6775
@override
6876
Object visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
6977
logger.error('Function typed formal parameters not supported '
7078
'(${node.toSource()})');
71-
return _visitNormalFormalParameter(null, node.identifier);
79+
return _visitNormalFormalParameter(node.metadata, null, node.identifier);
7280
}
7381

7482
@override
@@ -93,18 +101,30 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
93101
writer.print(')');
94102
return null;
95103
}
104+
105+
@override
106+
Object visitAnnotation(Annotation node) {
107+
var prefix =
108+
node.arguments != null && node.arguments.length > 0 ? 'const ' : '';
109+
visitNodeWithPrefix(prefix, node.name);
110+
visitNodeWithPrefix(".", node.constructorName);
111+
visitNode(node.arguments);
112+
return null;
113+
}
96114
}
97115

98116
/// ToSourceVisitor designed to print 'parameters' values for Angular2's
99117
/// [registerType] calls.
100118
class ParameterTransformVisitor extends _CtorTransformVisitor {
101-
ParameterTransformVisitor(PrintWriter writer) : super(writer);
119+
ParameterTransformVisitor(PrintWriter writer) : super(writer) {
120+
_withParameterNames = false;
121+
_withParameterTypes = true;
122+
_withParameterAnnotations = true;
123+
}
102124

103125
@override
104126
Object visitConstructorDeclaration(ConstructorDeclaration node) {
105127
_buildFieldMap(node);
106-
_withParameterNames = false;
107-
_withParameterTypes = true;
108128
writer.print('const [');
109129
visitNode(node.parameters);
110130
writer.print(']');
@@ -131,7 +151,9 @@ class ParameterTransformVisitor extends _CtorTransformVisitor {
131151
/// ToSourceVisitor designed to print 'factory' values for Angular2's
132152
/// [registerType] calls.
133153
class FactoryTransformVisitor extends _CtorTransformVisitor {
134-
FactoryTransformVisitor(PrintWriter writer) : super(writer);
154+
FactoryTransformVisitor(PrintWriter writer) : super(writer) {
155+
_withParameterAnnotations = false;
156+
}
135157

136158
@override
137159
Object visitConstructorDeclaration(ConstructorDeclaration node) {
@@ -142,6 +164,7 @@ class FactoryTransformVisitor extends _CtorTransformVisitor {
142164
writer.print(' => new ');
143165
visitNode(node.returnType);
144166
visitNodeWithPrefix(".", node.name);
167+
145168
_withParameterTypes = false;
146169
visitNode(node.parameters);
147170
return null;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
library angular2.test.transform.common.read_file;
2+
3+
import 'dart:io';
4+
5+
/// Smooths over differences in CWD between IDEs and running tests in Travis.
6+
String readFile(String path) {
7+
for (var myPath in [path, 'test/transform/${path}']) {
8+
var file = new File(myPath);
9+
if (file.existsSync()) {
10+
return file.readAsStringSync();
11+
}
12+
}
13+
return null;
14+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library angular2.test.transform.directive_processor.all_tests;
2+
3+
import 'dart:io';
4+
import 'package:barback/barback.dart';
5+
import 'package:angular2/src/transform/directive_processor/rewriter.dart';
6+
import 'package:angular2/src/transform/common/formatter.dart';
7+
import 'package:code_transformers/tests.dart';
8+
import 'package:dart_style/dart_style.dart';
9+
import 'package:path/path.dart' as path;
10+
import 'package:unittest/unittest.dart';
11+
import 'package:unittest/vm_config.dart';
12+
13+
import '../common/read_file.dart';
14+
15+
var formatter = new DartFormatter();
16+
17+
void allTests() {
18+
test('should preserve parameter annotations as const instances', () {
19+
var inputPath = 'parameter_metadata/soup.dart';
20+
var expected = _readFile('parameter_metadata/expected/soup.ngDeps.dart');
21+
var output =
22+
formatter.format(createNgDeps(_readFile(inputPath), inputPath));
23+
expect(output, equals(expected));
24+
});
25+
}
26+
27+
var pathBase = 'directive_processor';
28+
29+
/// Smooths over differences in CWD between IDEs and running tests in Travis.
30+
String _readFile(String path) => readFile('$pathBase/$path');
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
library dinner.soup;
2+
3+
import 'soup.dart';
4+
import 'package:angular2/src/core/annotations/annotations.dart';
5+
6+
bool _visited = false;
7+
void setupReflection(reflector) {
8+
if (_visited) return;
9+
_visited = true;
10+
reflector
11+
..registerType(SoupComponent, {
12+
"factory":
13+
(String description, salt) => new SoupComponent(description, salt),
14+
"parameters": const [const [Tasty, String], const [const Inject(Salt)]],
15+
"annotations": const [const Component(selector: '[soup]')]
16+
});
17+
} // {"version":1,"importOffset":104,"registerOffset":451,"imports":["package:angular2/src/core/annotations/annotations.dart"]}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
library dinner.soup;
2+
3+
import 'package:angular2/src/core/annotations/annotations.dart';
4+
5+
@Component(selector: '[soup]')
6+
class SoupComponent {
7+
SoupComponent(@Tasty String description, @Inject(Salt) salt);
8+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
library angular2.test.transform.integration;
2+
3+
import 'dart:io';
4+
import 'package:angular2/transformer.dart';
5+
import 'package:code_transformers/tests.dart';
6+
import 'package:dart_style/dart_style.dart';
7+
import 'package:unittest/unittest.dart';
8+
9+
import '../common/read_file.dart';
10+
11+
var formatter = new DartFormatter();
12+
var transform = new AngularTransformerGroup(new TransformerOptions(
13+
'web/index.dart', reflectionEntryPoint: 'web/index.dart'));
14+
15+
class IntegrationTestConfig {
16+
final String name;
17+
final Map<String, String> assetPathToInputPath;
18+
final Map<String, String> assetPathToExpectedOutputPath;
19+
20+
IntegrationTestConfig(this.name,
21+
{Map<String, String> inputs, Map<String, String> outputs})
22+
: this.assetPathToInputPath = inputs,
23+
this.assetPathToExpectedOutputPath = outputs;
24+
}
25+
26+
void allTests() {
27+
/*
28+
* Each test has its own directory for inputs & an `expected` directory for
29+
* expected outputs.
30+
*
31+
* In addition to these declared inputs, we inject a set of common inputs for
32+
* every test.
33+
*/
34+
var commonInputs = {
35+
'angular2|lib/src/core/annotations/annotations.dart':
36+
'../../../lib/src/core/annotations/annotations.dart',
37+
'angular2|lib/src/core/application.dart': '../common/application.dart',
38+
'angular2|lib/src/reflection/reflection_capabilities.dart':
39+
'../common/reflection_capabilities.dart'
40+
};
41+
42+
var tests = [
43+
new IntegrationTestConfig(
44+
'should generate proper code for a Component defining only a selector.',
45+
inputs: {
46+
'a|web/index.dart': 'simple_annotation_files/index.dart',
47+
'a|web/bar.dart': 'simple_annotation_files/bar.dart'
48+
},
49+
outputs: {
50+
'a|web/bar.ngDeps.dart':
51+
'simple_annotation_files/expected/bar.ngDeps.dart',
52+
'a|web/index.ngDeps.dart':
53+
'simple_annotation_files/expected/index.ngDeps.dart'
54+
}),
55+
new IntegrationTestConfig(
56+
'should generate proper code for a Component using a selector defined '
57+
'in another file.',
58+
inputs: {
59+
'a|web/index.dart': 'two_deps_files/index.dart',
60+
'a|web/foo.dart': 'two_deps_files/foo.dart',
61+
'a|web/bar.dart': 'two_deps_files/bar.dart'
62+
},
63+
outputs: {
64+
'a|web/bar.ngDeps.dart': 'two_deps_files/expected/bar.ngDeps.dart'
65+
}),
66+
new IntegrationTestConfig(
67+
'should generate proper code for a Component declaring a '
68+
'componentService defined in another file.',
69+
inputs: {
70+
'a|web/index.dart': 'list_of_types_files/index.dart',
71+
'a|web/foo.dart': 'list_of_types_files/foo.dart',
72+
'a|web/bar.dart': 'list_of_types_files/bar.dart'
73+
},
74+
outputs: {
75+
'a|web/bar.ngDeps.dart': 'list_of_types_files/expected/bar.ngDeps.dart'
76+
}),
77+
new IntegrationTestConfig(
78+
'should generate a factory for a class with no declared ctor.',
79+
inputs: {
80+
'a|web/index.dart': 'synthetic_ctor_files/index.dart',
81+
'a|web/bar.dart': 'synthetic_ctor_files/bar.dart'
82+
},
83+
outputs: {
84+
'a|web/bar.ngDeps.dart': 'synthetic_ctor_files/expected/bar.ngDeps.dart'
85+
}),
86+
new IntegrationTestConfig('should preserve multiple annotations.',
87+
inputs: {
88+
'a|web/index.dart': 'two_annotations_files/index.dart',
89+
'a|web/bar.dart': 'two_annotations_files/bar.dart',
90+
'angular2|lib/src/core/annotations/template.dart':
91+
'../../../lib/src/core/annotations/template.dart'
92+
},
93+
outputs: {
94+
'a|web/bar.ngDeps.dart': 'two_annotations_files/expected/bar.ngDeps.dart'
95+
}),
96+
new IntegrationTestConfig('should generate setters for `bind` values.',
97+
inputs: {
98+
'a|web/index.dart': 'basic_bind_files/index.dart',
99+
'a|web/bar.dart': 'basic_bind_files/bar.dart'
100+
},
101+
outputs: {
102+
'a|web/bar.ngDeps.dart': 'basic_bind_files/expected/bar.ngDeps.dart'
103+
}),
104+
new IntegrationTestConfig(
105+
'should ensure that dependencies are property chained.',
106+
inputs: {
107+
'a|web/index.dart': 'chained_deps_files/index.dart',
108+
'a|web/foo.dart': 'chained_deps_files/foo.dart',
109+
'a|web/bar.dart': 'chained_deps_files/bar.dart'
110+
},
111+
outputs: {
112+
'a|web/bar.ngDeps.dart': 'chained_deps_files/expected/bar.ngDeps.dart',
113+
'a|web/foo.ngDeps.dart': 'chained_deps_files/expected/foo.ngDeps.dart'
114+
})
115+
];
116+
117+
var cache = {};
118+
119+
for (var config in tests) {
120+
121+
// Read in input & output files.
122+
config.assetPathToInputPath
123+
..addAll(commonInputs)
124+
..forEach((key, value) {
125+
config.assetPathToInputPath[key] =
126+
cache.putIfAbsent(value, () => _readFile(value));
127+
});
128+
config.assetPathToExpectedOutputPath.forEach((key, value) {
129+
config.assetPathToExpectedOutputPath[key] = cache.putIfAbsent(value, () {
130+
var code = _readFile(value);
131+
return value.endsWith('dart') ? formatter.format(code) : code;
132+
});
133+
});
134+
testPhases(config.name, [
135+
[transform]
136+
], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []);
137+
}
138+
}
139+
140+
/// Smooths over differences in CWD between IDEs and running tests in Travis.
141+
String _readFile(String path) => readFile('integration/$path');

0 commit comments

Comments
 (0)