Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit eee4191

Browse files
mvuksanomhevery
authored andcommitted
feat(annotation): Annotations on superclasses are honored
This patch supports the following scenario: ``` class Base { @Ngattr('foo') var foo; } @ngdirective(selector:'my-directive') class MyDirective extends Base { @Ngattr('bar') var bar; } ``` MyDirective will now have both properties, foo and bar. Closes #829
1 parent f79696d commit eee4191

File tree

8 files changed

+208
-60
lines changed

8 files changed

+208
-60
lines changed

lib/core/registry_dynamic.dart

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,29 @@ class DynamicMetadataExtractor implements MetadataExtractor {
5757

5858

5959
Map<String, DirectiveAnnotation> fieldMetadataExtractor(Type type) =>
60-
_fieldMetadataCache.putIfAbsent(type, () => _fieldMetadataExtractor(type));
60+
_fieldMetadataCache.putIfAbsent(type, () => _fieldMetadataExtractor(reflectType(type)));
6161

62-
Map<String, DirectiveAnnotation> _fieldMetadataExtractor(Type type) {
63-
ClassMirror cm = reflectType(type);
64-
final fields = <String, DirectiveAnnotation>{};
65-
cm.declarations.forEach((Symbol name, DeclarationMirror decl) {
66-
if (decl is VariableMirror ||
67-
decl is MethodMirror && (decl.isGetter || decl.isSetter)) {
68-
var fieldName = MirrorSystem.getName(name);
69-
if (decl is MethodMirror && decl.isSetter) {
62+
Map<String, DirectiveAnnotation> _fieldMetadataExtractor(ClassMirror cm) {
63+
var fields = <String, DirectiveAnnotation>{};
64+
if(cm.superclass != null) {
65+
fields.addAll(_fieldMetadataExtractor(cm.superclass));
66+
} else {
67+
fields = {};
68+
}
69+
Map<Symbol, DeclarationMirror> declarations = cm.declarations;
70+
declarations.forEach((symbol, dm) {
71+
if(dm is VariableMirror ||
72+
dm is MethodMirror && (dm.isGetter || dm.isSetter)) {
73+
var fieldName = MirrorSystem.getName(symbol);
74+
if (dm is MethodMirror && dm.isSetter) {
7075
// Remove "=" from the end of the setter.
7176
fieldName = fieldName.substring(0, fieldName.length - 1);
7277
}
73-
decl.metadata.forEach((InstanceMirror meta) {
78+
dm.metadata.forEach((InstanceMirror meta) {
7479
if (_fieldAnnotations.contains(meta.type)) {
7580
if (fields.containsKey(fieldName)) {
7681
throw 'Attribute annotation for $fieldName is defined more '
77-
'than once in $type';
82+
'than once in ${cm.reflectedType}';
7883
}
7984
fields[fieldName] = meta.reflectee as DirectiveAnnotation;
8085
}

lib/tools/source_metadata_extractor.dart

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
library angular.source_metadata_extractor ;
22

33
import 'package:analyzer/src/generated/ast.dart';
4+
import 'package:analyzer/src/generated/element.dart';
45

56
import 'package:angular/tools/source_crawler.dart';
67
import 'package:angular/tools/common.dart';
@@ -181,34 +182,51 @@ class DirectiveMetadataCollectingAstVisitor extends RecursiveAstVisitor {
181182
}
182183
});
183184

184-
// Check fields/getters/setter for presense of attr mapping annotations.
185-
clazz.members.forEach((ClassMember member) {
186-
if (member is FieldDeclaration ||
187-
(member is MethodDeclaration &&
188-
(member.isSetter || member.isGetter))) {
189-
member.metadata.forEach((Annotation ann) {
190-
if (_attrAnnotationsToSpec.containsKey(ann.name.name)) {
191-
String fieldName;
192-
if (member is FieldDeclaration) {
193-
fieldName = member.fields.variables.first.name.name;
194-
} else { // MethodDeclaration
195-
fieldName = (member as MethodDeclaration).name.name;
196-
}
197-
StringLiteral attNameLiteral = ann.arguments.arguments.first;
198-
if (meta.attributeMappings
199-
.containsKey(attNameLiteral.stringValue)) {
200-
throw 'Attribute mapping already defined for '
201-
'${clazz.name}.$fieldName';
202-
}
203-
meta.attributeMappings[attNameLiteral.stringValue] =
204-
_attrAnnotationsToSpec[ann.name.name] + fieldName;
205-
}
206-
});
207-
}
208-
});
185+
if (meta != null) _walkSuperclassChain(clazz, meta, _extractMappingsFromClass);
209186
});
187+
210188
return super.visitClassDeclaration(clazz);
211189
}
190+
191+
_walkSuperclassChain(ClassDeclaration clazz, DirectiveMetadata meta,
192+
metadataExtractor(ClassDeclaration clazz, DirectiveMetadata meta)) {
193+
while (clazz != null) {
194+
metadataExtractor(clazz, meta);
195+
if (clazz.element != null && clazz.element.supertype != null) {
196+
clazz = clazz.element.supertype.element.node;
197+
} else {
198+
clazz = null;
199+
}
200+
}
201+
}
202+
203+
_extractMappingsFromClass(ClassDeclaration clazz, DirectiveMetadata meta) {
204+
// Check fields/getters/setter for presence of attr mapping annotations.
205+
clazz.members.forEach((ClassMember member) {
206+
if (member is FieldDeclaration ||
207+
(member is MethodDeclaration &&
208+
(member.isSetter || member.isGetter))) {
209+
member.metadata.forEach((Annotation ann) {
210+
if (_attrAnnotationsToSpec.containsKey(ann.name.name)) {
211+
String fieldName;
212+
if (member is FieldDeclaration) {
213+
fieldName = member.fields.variables.first.name.name;
214+
} else { // MethodDeclaration
215+
fieldName = (member as MethodDeclaration).name.name;
216+
}
217+
StringLiteral attNameLiteral = ann.arguments.arguments.first;
218+
if (meta.attributeMappings
219+
.containsKey(attNameLiteral.stringValue)) {
220+
throw 'Attribute mapping already defined for '
221+
'${clazz.name}.$fieldName';
222+
}
223+
meta.attributeMappings[attNameLiteral.stringValue] =
224+
_attrAnnotationsToSpec[ann.name.name] + fieldName;
225+
}
226+
});
227+
}
228+
});
229+
}
212230
}
213231

214232
class DirectiveMetadataCollectingVisitor {

lib/tools/transformer/metadata_extractor.dart

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -242,20 +242,31 @@ class AnnotationExtractor {
242242

243243
/// Extracts all of the annotations for the specified class.
244244
AnnotatedType extractAnnotations(ClassElement cls) {
245-
if (resolver.getImportUri(cls.library, from: outputId) == null) {
246-
warn('Dropping annotations for ${cls.name} because the '
247-
'containing file cannot be imported (must be in a lib folder).', cls);
248-
return null;
249-
}
250-
245+
var classElement = cls;
251246
var visitor = new _AnnotationVisitor(_annotationElements);
252-
cls.node.accept(visitor);
247+
while (classElement != null) {
248+
if (resolver.getImportUri(classElement.library, from: outputId) == null) {
249+
warn('Dropping annotations for ${classElement.name} because the '
250+
'containing file cannot be imported (must be in a lib folder).', classElement);
251+
return null;
252+
}
253+
if (classElement.node != null) {
254+
classElement.node.accept(visitor);
255+
}
256+
257+
if (classElement.supertype != null) {
258+
visitor.visitingSupertype = true;
259+
classElement = classElement.supertype.element;
260+
} else {
261+
classElement = null;
262+
}
263+
}
253264

254265
if (!visitor.hasAnnotations) return null;
255266

256267
var type = new AnnotatedType(cls);
257268
type.annotations = visitor.classAnnotations
258-
.where((annotation) {
269+
.where((Annotation annotation) {
259270
var element = annotation.element;
260271
if (element != null && !element.isPublic) {
261272
warn('Annotation $annotation is not public.',
@@ -277,6 +288,7 @@ class AnnotationExtractor {
277288
element.enclosingElement.type.isAssignableTo(formatterType.type);
278289
}).toList();
279290

291+
if (type.annotations.isEmpty) return null;
280292

281293
var memberAnnotations = {};
282294
visitor.memberAnnotations.forEach((memberName, annotations) {
@@ -293,8 +305,6 @@ class AnnotationExtractor {
293305
_foldMemberAnnotations(memberAnnotations, type);
294306
}
295307

296-
if (type.annotations.isEmpty) return null;
297-
298308
return type;
299309
}
300310

@@ -309,10 +319,6 @@ class AnnotationExtractor {
309319
return element.enclosingElement.type.isAssignableTo(
310320
directiveType.type);
311321
});
312-
if (ngAnnotations.isEmpty) {
313-
warn('Found field annotation but no class directives.', type.type);
314-
return;
315-
}
316322

317323
var mapType = resolver.getType('dart.core.Map').type;
318324
// Find acceptable constructors- ones which take a param named 'map'
@@ -410,15 +416,17 @@ class _AnnotationVisitor extends GeneralizingAstVisitor {
410416
final List<Element> allowedMemberAnnotations;
411417
final List<Annotation> classAnnotations = [];
412418
final Map<String, List<Annotation>> memberAnnotations = {};
419+
var visitingSupertype = false;
413420

414421
_AnnotationVisitor(this.allowedMemberAnnotations);
415422

416423
void visitAnnotation(Annotation annotation) {
417424
var parent = annotation.parent;
418425
if (parent is! Declaration) return;
419426

420-
if (parent.element is ClassElement) {
427+
if (parent.element is ClassElement && !visitingSupertype) {
421428
classAnnotations.add(annotation);
429+
422430
} else if (allowedMemberAnnotations.contains(annotation.element)) {
423431
if (parent is MethodDeclaration) {
424432
memberAnnotations.putIfAbsent(parent.name.name, () => [])

perf/pubspec.lock

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,15 @@ packages:
3030
code_transformers:
3131
description: code_transformers
3232
source: hosted
33-
version: "0.1.1"
33+
version: "0.1.3"
3434
collection:
3535
description: collection
3636
source: hosted
3737
version: "0.9.1"
3838
di:
39-
description:
40-
ref: null
41-
resolved-ref: a2de00d84934f4610d1f9e108c39614e66a773f5
42-
url: "git://github.com/angular/di.dart.git"
43-
source: git
44-
version: "0.0.34"
39+
description: di
40+
source: hosted
41+
version: "0.0.37"
4542
html5lib:
4643
description: html5lib
4744
source: hosted
@@ -73,7 +70,7 @@ packages:
7370
route_hierarchical:
7471
description: route_hierarchical
7572
source: hosted
76-
version: "0.4.15"
73+
version: "0.4.18"
7774
shadow_dom:
7875
description: shadow_dom
7976
source: hosted

test/core/core_directive_spec.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ void main() {
6767
'in Bad2Component');
6868
});
6969
});
70+
71+
describe("Inheritance", () {
72+
var element;
73+
var nodeAttrs;
74+
75+
beforeEachModule((Module module) {
76+
module..type(Sub)..type(Base);
77+
});
78+
79+
it("should extract attr map from annotated component which inherits other component", (DirectiveMap directives) {
80+
var annotations = directives.annotationsFor(Sub);
81+
expect(annotations.length).toEqual(1);
82+
expect(annotations[0] is Directive).toBeTruthy();
83+
84+
Directive annotation = annotations[0];
85+
expect(annotation.selector).toEqual('[sub]');
86+
expect(annotation.map).toEqual({
87+
"foo": "=>foo",
88+
"bar": "=>bar",
89+
"baz": "=>baz"
90+
});
91+
});
92+
});
7093
});
7194
}
7295

@@ -141,3 +164,18 @@ class Bad2Component {
141164
@NgOneWay('foo')
142165
set foo(val) {}
143166
}
167+
168+
@Decorator(selector: '[sub]')
169+
class Sub extends Base {
170+
@NgOneWay('bar')
171+
String bar;
172+
}
173+
174+
class Base {
175+
@NgOneWay('baz')
176+
String baz;
177+
178+
@NgOneWay('foo')
179+
String foo;
180+
}
181+

test/core/registry_spec.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,4 @@ class MyAnnotation {
6161
}
6262

6363
@MyAnnotation('A') @MyAnnotation('B') class A1 {}
64-
@MyAnnotation('A') class A2 {}
64+
@MyAnnotation('A') class A2 {}

test/tools/transformer/expression_generator_spec.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,39 @@ main() {
9191
symbols: []);
9292
});
9393

94+
it('should generate expressions for variables found in superclass', () {
95+
return generates(phases,
96+
inputs: {
97+
'a|web/main.dart': '''
98+
import 'package:angular/angular.dart';
99+
100+
@NgComponent(
101+
templateUrl: 'lib/foo.html',
102+
selector: 'my-component')
103+
class FooComponent extends BarComponent {
104+
@NgAttr('foo')
105+
var foo;
106+
}
107+
108+
class BarComponent {
109+
@NgAttr('bar')
110+
var bar;
111+
}
112+
113+
main() {}
114+
''',
115+
'a|lib/foo.html': '''
116+
<div>{{template.foo}}</div>
117+
<div>{{template.bar}}</div>''',
118+
'a|web/index.html': '''
119+
<script src='main.dart' type='application/dart'></script>''',
120+
'angular|lib/angular.dart': libAngular,
121+
},
122+
getters: ['foo', 'bar', 'template'],
123+
setters: ['foo', 'bar', 'template'],
124+
symbols: []);
125+
});
126+
94127
it('should apply additional HTML files', () {
95128
htmlFiles.add('web/dummy.html');
96129
htmlFiles.add('/packages/b/bar.html');
@@ -186,4 +219,9 @@ library angular.core.annotation_src;
186219
class Component {
187220
const Component({String templateUrl, String selector});
188221
}
222+
223+
class NgAttr {
224+
final _mappingSpec = '@';
225+
const NgAttr(String attrName);
226+
}
189227
''';

0 commit comments

Comments
 (0)