Skip to content

Commit 8ad4ad5

Browse files
author
Tim Blasi
committed
feat(dart/transform): Populate lifecycle from lifecycle interfaces
When a `Directive` implements a lifecycle interface (e.g. `OnChange` or `OnInit`), populate its `lifecycle` property if not already populated). Closes angular#3181
1 parent 854b5b7 commit 8ad4ad5

File tree

17 files changed

+571
-175
lines changed

17 files changed

+571
-175
lines changed

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

Lines changed: 62 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -2,176 +2,97 @@ library angular2.transform.common.annotation_matcher;
22

33
import 'package:analyzer/src/generated/ast.dart';
44
import 'package:barback/barback.dart' show AssetId;
5-
import 'package:code_transformers/assets.dart';
6-
import 'package:path/path.dart' as path;
7-
import 'logging.dart' show logger;
5+
import 'class_matcher_base.dart';
86

9-
/// [AnnotationDescriptor]s for the default angular annotations that can appear
7+
export 'class_matcher_base.dart' show ClassDescriptor;
8+
9+
/// [ClassDescriptor]s for the default angular annotations that can appear
1010
/// on a class. These classes are re-exported in many places so this covers all
1111
/// the possible libraries which could provide them.
1212
const INJECTABLES = const [
13-
const AnnotationDescriptor(
14-
'Injectable', 'package:angular2/src/di/decorators.dart', null),
15-
const AnnotationDescriptor('Injectable', 'package:angular2/di.dart', null),
16-
const AnnotationDescriptor(
17-
'Injectable', 'package:angular2/angular2.dart', null),
13+
const ClassDescriptor(
14+
'Injectable', 'package:angular2/src/di/decorators.dart'),
15+
const ClassDescriptor('Injectable', 'package:angular2/di.dart'),
16+
const ClassDescriptor('Injectable', 'package:angular2/angular2.dart'),
1817
];
1918

2019
const DIRECTIVES = const [
21-
const AnnotationDescriptor('Directive',
22-
'package:angular2/src/core/annotations/annotations.dart', 'Injectable'),
23-
const AnnotationDescriptor('Directive',
24-
'package:angular2/src/core/annotations/decorators.dart', 'Injectable'),
25-
const AnnotationDescriptor('Directive',
20+
const ClassDescriptor(
21+
'Directive', 'package:angular2/src/core/annotations/annotations.dart',
22+
superClass: 'Injectable'),
23+
const ClassDescriptor(
24+
'Directive', 'package:angular2/src/core/annotations/decorators.dart',
25+
superClass: 'Injectable'),
26+
const ClassDescriptor('Directive',
2627
'package:angular2/src/core/annotations_impl/annotations.dart',
27-
'Injectable'),
28-
const AnnotationDescriptor(
29-
'Directive', 'package:angular2/annotations.dart', 'Injectable'),
30-
const AnnotationDescriptor(
31-
'Directive', 'package:angular2/angular2.dart', 'Injectable'),
32-
const AnnotationDescriptor(
33-
'Directive', 'package:angular2/core.dart', 'Injectable'),
28+
superClass: 'Injectable'),
29+
const ClassDescriptor('Directive', 'package:angular2/annotations.dart',
30+
superClass: 'Injectable'),
31+
const ClassDescriptor('Directive', 'package:angular2/angular2.dart',
32+
superClass: 'Injectable'),
33+
const ClassDescriptor('Directive', 'package:angular2/core.dart',
34+
superClass: 'Injectable'),
3435
];
3536

3637
const COMPONENTS = const [
37-
const AnnotationDescriptor('Component',
38-
'package:angular2/src/core/annotations/annotations.dart', 'Directive'),
39-
const AnnotationDescriptor('Component',
40-
'package:angular2/src/core/annotations/decorators.dart', 'Directive'),
41-
const AnnotationDescriptor('Component',
38+
const ClassDescriptor(
39+
'Component', 'package:angular2/src/core/annotations/annotations.dart',
40+
superClass: 'Directive'),
41+
const ClassDescriptor(
42+
'Component', 'package:angular2/src/core/annotations/decorators.dart',
43+
superClass: 'Directive'),
44+
const ClassDescriptor('Component',
4245
'package:angular2/src/core/annotations_impl/annotations.dart',
43-
'Directive'),
44-
const AnnotationDescriptor(
45-
'Component', 'package:angular2/annotations.dart', 'Directive'),
46-
const AnnotationDescriptor(
47-
'Component', 'package:angular2/angular2.dart', 'Directive'),
48-
const AnnotationDescriptor(
49-
'Component', 'package:angular2/core.dart', 'Directive'),
46+
superClass: 'Directive'),
47+
const ClassDescriptor('Component', 'package:angular2/annotations.dart',
48+
superClass: 'Directive'),
49+
const ClassDescriptor('Component', 'package:angular2/angular2.dart',
50+
superClass: 'Directive'),
51+
const ClassDescriptor('Component', 'package:angular2/core.dart',
52+
superClass: 'Directive'),
5053
];
5154

5255
const VIEWS = const [
53-
const AnnotationDescriptor('View', 'package:angular2/view.dart', null),
54-
const AnnotationDescriptor('View', 'package:angular2/angular2.dart', null),
55-
const AnnotationDescriptor('View', 'package:angular2/core.dart', null),
56-
const AnnotationDescriptor(
57-
'View', 'package:angular2/src/core/annotations/view.dart', null),
58-
const AnnotationDescriptor(
59-
'View', 'package:angular2/src/core/annotations_impl/view.dart', null),
56+
const ClassDescriptor('View', 'package:angular2/view.dart'),
57+
const ClassDescriptor('View', 'package:angular2/angular2.dart'),
58+
const ClassDescriptor('View', 'package:angular2/core.dart'),
59+
const ClassDescriptor(
60+
'View', 'package:angular2/src/core/annotations/view.dart'),
61+
const ClassDescriptor(
62+
'View', 'package:angular2/src/core/annotations_impl/view.dart'),
6063
];
6164

6265
/// Checks if a given [Annotation] matches any of the given
63-
/// [AnnotationDescriptors].
64-
class AnnotationMatcher {
65-
/// Always start out with the default angular [AnnotationDescriptor]s.
66-
final List<AnnotationDescriptor> _annotations = []
67-
..addAll(VIEWS)
68-
..addAll(COMPONENTS)
69-
..addAll(INJECTABLES)
70-
..addAll(DIRECTIVES);
71-
72-
AnnotationMatcher();
73-
74-
/// Adds a new [AnnotationDescriptor].
75-
void add(AnnotationDescriptor annotation) => _annotations.add(annotation);
76-
77-
/// Adds a number of [AnnotationDescriptor]s.
78-
void addAll(Iterable<AnnotationDescriptor> annotations) =>
79-
_annotations.addAll(annotations);
80-
81-
/// Returns the first [AnnotationDescriptor] that matches the given
82-
/// [Annotation] node which appears in `assetId`.
83-
AnnotationDescriptor firstMatch(Annotation annotation, AssetId assetId) =>
84-
_annotations.firstWhere((a) => _matchAnnotation(annotation, a, assetId),
85-
orElse: () => null);
66+
/// [ClassDescriptors].
67+
class AnnotationMatcher extends ClassMatcherBase {
68+
AnnotationMatcher._(classDescriptors) : super(classDescriptors);
69+
70+
factory AnnotationMatcher() {
71+
return new AnnotationMatcher._([]
72+
..addAll(COMPONENTS)
73+
..addAll(DIRECTIVES)
74+
..addAll(INJECTABLES)
75+
..addAll(VIEWS));
76+
}
8677

87-
/// Checks whether an [Annotation] node matches any [AnnotationDescriptor].
88-
bool hasMatch(Annotation annotation, AssetId assetId) =>
89-
_annotations.any((a) => _matchAnnotation(annotation, a, assetId));
78+
bool _implementsWithWarning(
79+
ClassDescriptor descriptor, List<ClassDescriptor> interfaces) =>
80+
implements(descriptor, interfaces,
81+
missingSuperClassWarning: 'Missing `custom_annotation` entry for `${descriptor.superClass}`.');
9082

9183
/// Checks if an [Annotation] node implements [Injectable].
9284
bool isInjectable(Annotation annotation, AssetId assetId) =>
93-
_implements(firstMatch(annotation, assetId), INJECTABLES);
85+
_implementsWithWarning(firstMatch(annotation.name, assetId), INJECTABLES);
9486

9587
/// Checks if an [Annotation] node implements [Directive].
9688
bool isDirective(Annotation annotation, AssetId assetId) =>
97-
_implements(firstMatch(annotation, assetId), DIRECTIVES);
89+
_implementsWithWarning(firstMatch(annotation.name, assetId), DIRECTIVES);
9890

9991
/// Checks if an [Annotation] node implements [Component].
10092
bool isComponent(Annotation annotation, AssetId assetId) =>
101-
_implements(firstMatch(annotation, assetId), COMPONENTS);
93+
_implementsWithWarning(firstMatch(annotation.name, assetId), COMPONENTS);
10294

10395
/// Checks if an [Annotation] node implements [View].
10496
bool isView(Annotation annotation, AssetId assetId) =>
105-
_implements(firstMatch(annotation, assetId), VIEWS);
106-
107-
/// Checks if `descriptor` extends or is any of the supplied `interfaces`.
108-
bool _implements(
109-
AnnotationDescriptor descriptor, List<AnnotationDescriptor> interfaces) {
110-
if (descriptor == null) return false;
111-
if (interfaces.contains(descriptor)) return true;
112-
if (descriptor.superClass == null) return false;
113-
var superClass = _annotations.firstWhere(
114-
(a) => a.name == descriptor.superClass, orElse: () => null);
115-
if (superClass == null) {
116-
logger.warning(
117-
'Missing `custom_annotation` entry for `${descriptor.superClass}`.');
118-
return false;
119-
}
120-
return _implements(superClass, interfaces);
121-
}
122-
123-
// Checks if an [Annotation] matches an [AnnotationDescriptor].
124-
static bool _matchAnnotation(
125-
Annotation annotation, AnnotationDescriptor descriptor, AssetId assetId) {
126-
String name;
127-
Identifier prefix;
128-
if (annotation.name is PrefixedIdentifier) {
129-
// TODO(jakemac): Shouldn't really need a cast here, remove once
130-
// https://github.com/dart-lang/sdk/issues/23798 is fixed.
131-
var prefixedName = annotation.name as PrefixedIdentifier;
132-
name = prefixedName.identifier.name;
133-
prefix = prefixedName.prefix;
134-
} else {
135-
name = annotation.name.name;
136-
}
137-
if (name != descriptor.name) return false;
138-
return (annotation.root as CompilationUnit).directives
139-
.where((d) => d is ImportDirective)
140-
.any((ImportDirective i) {
141-
var importMatch = false;
142-
var uriString = i.uri.stringValue;
143-
if (uriString == descriptor.import) {
144-
importMatch = true;
145-
} else if (uriString.startsWith('package:') ||
146-
uriString.startsWith('dart:')) {
147-
return false;
148-
} else {
149-
importMatch = descriptor.assetId ==
150-
uriToAssetId(assetId, uriString, logger, null);
151-
}
152-
153-
if (!importMatch) return false;
154-
if (prefix == null) return i.prefix == null;
155-
if (i.prefix == null) return false;
156-
return prefix.name == i.prefix.name;
157-
});
158-
}
159-
}
160-
161-
/// String based description of an annotation class and its location.
162-
class AnnotationDescriptor {
163-
/// The name of the class.
164-
final String name;
165-
/// A `package:` style import path to the file where the class is defined.
166-
final String import;
167-
/// The class that this class extends or implements. This is the only optional
168-
/// field.
169-
final String superClass;
170-
171-
AssetId get assetId => new AssetId(package, packagePath);
172-
String get package => path.split(import.replaceFirst('package:', '')).first;
173-
String get packagePath => path.joinAll(['lib']
174-
..addAll(path.split(import.replaceFirst('package:', ''))..removeAt(0)));
175-
176-
const AnnotationDescriptor(this.name, this.import, this.superClass);
97+
_implementsWithWarning(firstMatch(annotation.name, assetId), VIEWS);
17798
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
library angular2.transform.common.class_matcher_base;
2+
3+
import 'package:analyzer/src/generated/ast.dart';
4+
import 'package:barback/barback.dart' show AssetId;
5+
import 'package:code_transformers/assets.dart';
6+
import 'package:path/path.dart' as path;
7+
import 'logging.dart' show logger;
8+
9+
/// Checks if a given [Identifier] matches any of the given [ClassDescriptor]s.
10+
abstract class ClassMatcherBase {
11+
/// Always start out with the default angular [ClassDescriptor]s.
12+
final List<ClassDescriptor> _classDescriptors;
13+
14+
ClassMatcherBase(this._classDescriptors);
15+
16+
/// Adds a new [ClassDescriptor].
17+
void add(ClassDescriptor classDescriptor) =>
18+
_classDescriptors.add(classDescriptor);
19+
20+
/// Adds a number of [ClassDescriptor]s.
21+
void addAll(Iterable<ClassDescriptor> classDescriptors) =>
22+
_classDescriptors.addAll(classDescriptors);
23+
24+
/// Returns the first [ClassDescriptor] that matches the given
25+
/// [Identifier] node which appears in `assetId`.
26+
ClassDescriptor firstMatch(Identifier className, AssetId assetId) =>
27+
_classDescriptors.firstWhere((a) => isMatch(className, a, assetId),
28+
orElse: () => null);
29+
30+
/// Checks whether an [Identifier] matches any [ClassDescriptor].
31+
bool hasMatch(Identifier className, AssetId assetId) =>
32+
_classDescriptors.any((a) => isMatch(className, a, assetId));
33+
34+
/// Checks whether an [Identifier] matches any [ClassDescriptor].
35+
ImportDirective getMatchingImport(Identifier className, AssetId assetId) {
36+
for (var d in _classDescriptors) {
37+
var matchingImport = _getMatchingImport(className, d, assetId);
38+
if (matchingImport != null) {
39+
return matchingImport;
40+
}
41+
}
42+
return null;
43+
}
44+
45+
/// Checks if `descriptor` extends or is any of the supplied `interfaces`.
46+
bool implements(ClassDescriptor descriptor, List<ClassDescriptor> interfaces,
47+
{String missingSuperClassWarning}) {
48+
if (descriptor == null) return false;
49+
if (interfaces.contains(descriptor)) return true;
50+
if (descriptor.superClass == null) return false;
51+
var superClass = _classDescriptors.firstWhere(
52+
(a) => a.name == descriptor.superClass, orElse: () => null);
53+
if (superClass == null) {
54+
if (missingSuperClassWarning != null &&
55+
missingSuperClassWarning.isNotEmpty) {
56+
logger.warning(missingSuperClassWarning);
57+
}
58+
return false;
59+
}
60+
return implements(superClass, interfaces);
61+
}
62+
}
63+
64+
// Returns an [ImportDirective] matching `descriptor` for `className` which appears in `assetId`, or `null` if none exists.
65+
ImportDirective _getMatchingImport(
66+
Identifier className, ClassDescriptor descriptor, AssetId assetId) {
67+
if (className == null) return null;
68+
String name;
69+
Identifier prefix;
70+
if (className is PrefixedIdentifier) {
71+
name = className.identifier.name;
72+
prefix = className.prefix;
73+
} else {
74+
name = className.name;
75+
}
76+
if (name != descriptor.name) return null;
77+
return (className.root as CompilationUnit).directives
78+
.where((d) => d is ImportDirective)
79+
.firstWhere((ImportDirective i) {
80+
var importMatch = false;
81+
var uriString = i.uri.stringValue;
82+
if (uriString == descriptor.import) {
83+
importMatch = true;
84+
} else if (uriString.startsWith('package:') ||
85+
uriString.startsWith('dart:')) {
86+
return false;
87+
} else {
88+
importMatch =
89+
descriptor.assetId == uriToAssetId(assetId, uriString, logger, null);
90+
}
91+
92+
if (!importMatch) return false;
93+
if (prefix == null) return i.prefix == null;
94+
if (i.prefix == null) return false;
95+
return prefix.name == i.prefix.name;
96+
}, orElse: () => null);
97+
}
98+
99+
// Checks if `className` which appears in `assetId` matches a [ClassDescriptor].
100+
bool isMatch(
101+
Identifier className, ClassDescriptor descriptor, AssetId assetId) {
102+
return _getMatchingImport(className, descriptor, assetId) != null;
103+
}
104+
105+
/// String based description of a class and its location.
106+
class ClassDescriptor {
107+
/// The name of the class.
108+
final String name;
109+
/// A `package:` style import path to the file where the class is defined.
110+
final String import;
111+
/// The class that this class extends or implements. This is the only optional
112+
/// field.
113+
final String superClass;
114+
115+
AssetId get assetId => new AssetId(package, packagePath);
116+
String get package => path.split(import.replaceFirst('package:', '')).first;
117+
String get packagePath => path.joinAll(['lib']
118+
..addAll(path.split(import.replaceFirst('package:', ''))..removeAt(0)));
119+
120+
const ClassDescriptor(this.name, this.import, {this.superClass});
121+
}

0 commit comments

Comments
 (0)