Skip to content

Commit 01d5c29

Browse files
committed
fix(transformer): remove classDefParser in favor of hardcoded strings to speed up build
1 parent 7844e3a commit 01d5c29

12 files changed

Lines changed: 353 additions & 105 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
library angular2.transform.common.annotation_matcher;
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+
/// [AnnotationDescriptor]s for the default angular annotations that can appear
10+
/// on a class. These classes are re-exported in many places so this covers all
11+
/// the possible libraries which could provide them.
12+
const INJECTABLES = const [
13+
const AnnotationDescriptor(
14+
'Injectable', 'package:angular2/src/di/annotations.dart', null),
15+
const AnnotationDescriptor(
16+
'Injectable', 'package:angular2/src/di/annotations_impl.dart', null),
17+
const AnnotationDescriptor(
18+
'Injectable', 'package:angular2/src/di/decorators.dart', null),
19+
const AnnotationDescriptor('Injectable', 'package:angular2/di.dart', null),
20+
const AnnotationDescriptor(
21+
'Injectable', 'package:angular2/angular2.dart', null),
22+
];
23+
24+
const DIRECTIVES = const [
25+
const AnnotationDescriptor('Directive',
26+
'package:angular2/src/core/annotations/annotations.dart', 'Injectable'),
27+
const AnnotationDescriptor('Directive',
28+
'package:angular2/src/core/annotations_impl/annotations.dart',
29+
'Injectable'),
30+
const AnnotationDescriptor(
31+
'Directive', 'package:angular2/annotations.dart', 'Injectable'),
32+
const AnnotationDescriptor(
33+
'Directive', 'package:angular2/angular2.dart', 'Injectable'),
34+
const AnnotationDescriptor(
35+
'Directive', 'package:angular2/core.dart', 'Injectable'),
36+
];
37+
38+
const COMPONENTS = const [
39+
const AnnotationDescriptor('Component',
40+
'package:angular2/src/core/annotations/annotations.dart', 'Directive'),
41+
const AnnotationDescriptor('Component',
42+
'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'),
50+
];
51+
52+
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),
60+
];
61+
62+
/// 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);
86+
87+
/// Checks whether an [Annotation] node matches any [AnnotationDescriptor].
88+
bool hasMatch(Annotation annotation, AssetId assetId) =>
89+
_annotations.any((a) => _matchAnnotation(annotation, a, assetId));
90+
91+
/// Checks if an [Annotation] node implements [Injectable].
92+
bool isInjectable(Annotation annotation, AssetId assetId) =>
93+
_implements(firstMatch(annotation, assetId), INJECTABLES);
94+
95+
/// Checks if an [Annotation] node implements [Directive].
96+
bool isDirective(Annotation annotation, AssetId assetId) =>
97+
_implements(firstMatch(annotation, assetId), DIRECTIVES);
98+
99+
/// Checks if an [Annotation] node implements [Component].
100+
bool isComponent(Annotation annotation, AssetId assetId) =>
101+
_implements(firstMatch(annotation, assetId), COMPONENTS);
102+
103+
/// Checks if an [Annotation] node implements [View].
104+
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+
if (annotation.name.name != descriptor.name) return false;
127+
return (annotation.root as CompilationUnit).directives
128+
.where((d) => d is ImportDirective)
129+
.any((ImportDirective i) {
130+
var uriString = i.uri.stringValue;
131+
if (uriString == descriptor.import) return true;
132+
if (uriString.startsWith('package:') || uriString.startsWith('dart:')) {
133+
return false;
134+
}
135+
return descriptor.assetId ==
136+
uriToAssetId(assetId, uriString, logger, null);
137+
});
138+
}
139+
}
140+
141+
/// String based description of an annotation class and its location.
142+
class AnnotationDescriptor {
143+
/// The name of the class.
144+
final String name;
145+
/// A `package:` style import path to the file where the class is defined.
146+
final String import;
147+
/// The class that this class extends or implements. This is the only optional
148+
/// field.
149+
final String superClass;
150+
151+
AssetId get assetId => new AssetId(package, packagePath);
152+
String get package => path.split(import.replaceFirst('package:', '')).first;
153+
String get packagePath => path.joinAll(['lib']
154+
..addAll(path.split(import.replaceFirst('package:', ''))..removeAt(0)));
155+
156+
const AnnotationDescriptor(this.name, this.import, this.superClass);
157+
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
library angular2.transform.common.options;
22

3+
import 'annotation_matcher.dart';
34
import 'mirror_mode.dart';
45

56
const ENTRY_POINT_PARAM = 'entry_points';
67
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_points';
8+
const CUSTOM_ANNOTATIONS_PARAM = 'custom_annotations';
79

810
/// Provides information necessary to transform an Angular2 app.
911
class TransformerOptions {
@@ -23,16 +25,23 @@ class TransformerOptions {
2325
/// Whether to generate calls to our generated `initReflector` code
2426
final bool initReflector;
2527

28+
/// The [AnnotationMatcher] which is used to identify angular annotations.
29+
final AnnotationMatcher annotationMatcher;
30+
2631
TransformerOptions._internal(this.entryPoints, this.reflectionEntryPoints,
27-
this.modeName, this.mirrorMode, this.initReflector);
32+
this.modeName, this.mirrorMode, this.initReflector,
33+
this.annotationMatcher);
2834

2935
factory TransformerOptions(List<String> entryPoints,
3036
{List<String> reflectionEntryPoints, String modeName: 'release',
31-
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true}) {
37+
MirrorMode mirrorMode: MirrorMode.none, bool initReflector: true,
38+
List<AnnotationDescriptor> customAnnotationDescriptors: const []}) {
3239
if (reflectionEntryPoints == null || reflectionEntryPoints.isEmpty) {
3340
reflectionEntryPoints = entryPoints;
3441
}
42+
var annotationMatcher = new AnnotationMatcher()
43+
..addAll(customAnnotationDescriptors);
3544
return new TransformerOptions._internal(entryPoints, reflectionEntryPoints,
36-
modeName, mirrorMode, initReflector);
45+
modeName, mirrorMode, initReflector, annotationMatcher);
3746
}
3847
}

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
library angular2.transform.common.options_reader;
22

33
import 'package:barback/barback.dart';
4+
import 'annotation_matcher.dart';
45
import 'mirror_mode.dart';
56
import 'options.dart';
67

@@ -29,7 +30,8 @@ TransformerOptions parseBarbackSettings(BarbackSettings settings) {
2930
reflectionEntryPoints: reflectionEntryPoints,
3031
modeName: settings.mode.name,
3132
mirrorMode: mirrorMode,
32-
initReflector: initReflector);
33+
initReflector: initReflector,
34+
customAnnotationDescriptors: _readCustomAnnotations(config));
3335
}
3436

3537
/// Cribbed from the polymer project.
@@ -53,3 +55,48 @@ List<String> _readFileList(Map config, String paramName) {
5355
}
5456
return files;
5557
}
58+
59+
/// Parse the [CUSTOM_ANNOTATIONS_PARAM] options out of the transformer into
60+
/// [AnnotationDescriptor]s.
61+
List<AnnotationDescriptor> _readCustomAnnotations(Map config) {
62+
var descriptors = [];
63+
var customAnnotations = config[CUSTOM_ANNOTATIONS_PARAM];
64+
if (customAnnotations == null) return descriptors;
65+
var error = false;
66+
if (customAnnotations is! List) {
67+
error = true;
68+
} else {
69+
for (var description in customAnnotations) {
70+
if (description is! Map) {
71+
error = true;
72+
continue;
73+
}
74+
var name = description['name'];
75+
var import = description['import'];
76+
var superClass = description['superClass'];
77+
if (name == null || import == null || superClass == null) {
78+
error = true;
79+
continue;
80+
}
81+
descriptors.add(new AnnotationDescriptor(name, import, superClass));
82+
}
83+
}
84+
if (error) {
85+
print(CUSTOM_ANNOTATIONS_ERROR);
86+
}
87+
return descriptors;
88+
}
89+
90+
const CUSTOM_ANNOTATIONS_ERROR = '''
91+
Invalid value for $CUSTOM_ANNOTATIONS_PARAM in the Angular2 transformer.
92+
Expected something that looks like the following:
93+
94+
transformers:
95+
- angular2:
96+
custom_annotations:
97+
- name: MyAnnotation
98+
import: 'package:my_package/my_annotation.dart'
99+
superClass: Component
100+
- name: ...
101+
import: ...
102+
superClass: ...''';

0 commit comments

Comments
 (0)