Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(476)

Side by Side Diff: lib/build/initializer_plugin.dart

Issue 923733002: Major refactor of the transformer, added an `InitializePlugin` class which allows you to hook direc… (Closed) Base URL: git@github.com:dart-lang/static-init.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4 library initialize.build.initializer_plugin;
5
6 import 'package:analyzer/src/generated/ast.dart';
7 import 'package:analyzer/src/generated/element.dart';
8 import 'package:barback/barback.dart';
9 import 'package:code_transformers/resolver.dart';
10 import 'package:initialize/transformer.dart';
11 import 'package:path/path.dart' as path;
12
13 /// A plug which allows an initializer to write out an [InitEntry] given some
14 /// [InitializerData] from an annotation that was found.
15 abstract class InitializerPlugin {
16 /// Whether or not this plugin should be applied to an [Initializer] given
17 /// some [InitializerData]. If [true] is returned then this plugin will take
18 /// ownership of this [InitializerData] and no subsequent plugins will have
19 /// an opportunity to access it.
20 bool shouldApply(InitializerPluginData data);
21
22 /// Returns a [String] or [null]. The [String] should represent dart code
23 /// which creates a new [InitEntry] and that entry is added to the static
24 /// initializers list. If [null] is returned then no entry is added at all for
25 /// this [InitializerData].
26 String apply(InitializerPluginData data);
27 }
28
29 /// A class which wraps all the default data passed to an [InitializerPlugin]
30 /// for each annotation.
31 class InitializerPluginData {
32 final InitializerData initializer;
33 final AssetId bootstrapId;
34 final Map<LibraryElement, String> libraryPrefixes;
35 final TransformLogger logger;
36 final Resolver resolver;
37 InitializerPluginData(this.initializer, this.bootstrapId,
38 this.libraryPrefixes, this.resolver, this.logger);
39 }
40
41 /// The basic [InitializerPlugin]. This generates a new [InitEntry] to be added
42 /// to the static initializers list, and applies to every item it sees.
43 class DefaultInitializerPlugin implements InitializerPlugin {
44 const DefaultInitializerPlugin();
45
46 /// Applies to everything. Put other plugins before this one to override this
47 /// behaviour.
48 bool shouldApply(InitializerPluginData data) => true;
49
50 /// Creates a normal [InitEntry] string.
51 String apply(InitializerPluginData pluginData) {
52 var target = buildTarget(pluginData);
53 var meta = buildMeta(pluginData);
54 return ' new InitEntry($meta, $target)';
55 }
56
57 /// Builds a [String] representing the meta of an [InitEntry] given an
58 /// [ElementAnnotation] that was found.
59 String buildMeta(InitializerPluginData pluginData) {
60 var logger = pluginData.logger;
61 var element = pluginData.initializer.element;
62 var elementAnnotation = pluginData.initializer.elementAnnotation;
63 var elementAnnotationElement = elementAnnotation.element;
64 var libraryPrefixes = pluginData.libraryPrefixes;
65 if (elementAnnotationElement is ConstructorElement) {
66 return buildConstructorMeta(elementAnnotation, pluginData);
67 } else if (elementAnnotationElement is PropertyAccessorElement) {
68 return buildPropertyMeta(elementAnnotation, pluginData);
69 } else {
70 logger.error('Unsupported annotation type. Only constructors and '
71 'properties are supported as initializers.');
72 }
73 return null;
74 }
75
76 /// Builds a [String] representing the meta of an [InitEntry] given an
77 /// [ElementAnnotation] whose element was a [ConstructorElement].
78 String buildConstructorMeta(
79 ElementAnnotation elementAnnotation, InitializerPluginData pluginData) {
80 var logger = pluginData.logger;
81 var node = pluginData.initializer.node;
82 var metaPrefix = pluginData.libraryPrefixes[
83 elementAnnotation.element.library];
84
85 var annotation = pluginData.initializer.annotation;
86 if (annotation == null) {
87 logger.error(
88 'Initializer annotations are only supported on libraries, classes, '
89 'and top level methods. Found $node.');
90 }
91 var clazz = annotation.name;
92 var constructor = annotation.constructorName == null
93 ? ''
94 : '.${annotation.constructorName}';
95 // TODO(jakemac): Support more than raw values here
96 // https://github.com/dart-lang/static_init/issues/5
97 var args = buildArgumentList(annotation.arguments, pluginData);
98 return 'const $metaPrefix.${clazz}$constructor$args';
99 }
100
101 /// Builds a [String] representing the meta of an [InitEntry] given an
102 /// [ElementAnnotation] whose element was a [PropertyAccessorElement].
103 String buildPropertyMeta(
104 ElementAnnotation annotation, InitializerPluginData pluginData) {
105 var metaPrefix = pluginData.libraryPrefixes[annotation.element.library];
106 return '$metaPrefix.${annotation.element.name}';
107 }
108
109 /// Builds a [String] for the target of an [InitEntry] given an [Element] that
110 /// was annotated.
111 String buildTarget(InitializerPluginData pluginData) {
112 var element = pluginData.initializer.element;
113 var logger = pluginData.logger;
114 if (element is LibraryElement) {
115 return buildLibraryTarget(element, pluginData);
116 } else if (element is ClassElement) {
117 return buildClassTarget(element, pluginData);
118 } else if (element is FunctionElement) {
119 return buildFunctionTarget(element, pluginData);
120 } else {
121 logger.error('Initializers can only be applied to top level functions, '
122 'libraries, and classes.');
123 }
124 return null;
125 }
126
127 /// Builds a [String] for the target of an [InitEntry] given a [ClassElement]
128 /// that was annotated.
129 String buildClassTarget(Element element, InitializerPluginData pluginData) =>
Siggi Cherem (dart-lang) 2015/02/13 00:50:36 let's change the type here Element -> ClassElement
jakemac 2015/02/13 20:16:05 Done.
130 buildSimpleTarget(element, pluginData);
131
132 /// Builds a [String] for the target of an [InitEntry] given a [FunctionElemen t]
Siggi Cherem (dart-lang) 2015/02/13 00:50:36 nit - 80 col (here and below)
jakemac 2015/02/13 20:16:05 Done.
133 /// that was annotated.
134 String buildFunctionTarget(
135 Element element, InitializerPluginData pluginData) =>
Siggi Cherem (dart-lang) 2015/02/13 00:50:36 same here and with LibraryTarget below
jakemac 2015/02/13 20:16:05 Done.
136 buildSimpleTarget(element, pluginData);
137
138 /// Builds a [String] for the target of an [InitEntry] for a simple [Element].
139 /// This is just the library prefix followed by the element name.
140 String buildSimpleTarget(Element element, InitializerPluginData pluginData) =>
141 '${pluginData.libraryPrefixes[element.library]}.${element.name}';
142
143 /// Builds a [String] for the target of an [InitEntry] given a [LibraryElement ]
144 /// that was annotated.
145 String buildLibraryTarget(Element element, InitializerPluginData pluginData) {
146 var bootstrapId = pluginData.bootstrapId;
147 var logger = pluginData.logger;
148 var segments = element.source.uri.pathSegments;
149 var package = segments[0];
150 var libraryPath;
151 var packageString;
152 if (bootstrapId.package == package &&
153 bootstrapId.path.startsWith('${segments[1]}/')) {
154 // reset `package` to null, we will do a relative path in this case.
155 packageString = 'null';
156 libraryPath = path.url.relative(
157 path.url.joinAll(segments.getRange(1, segments.length)),
158 from: path.url.dirname(path.url.join(bootstrapId.path)));
159 } else if (segments[1] == 'lib') {
160 packageString = "'$package'";
161 libraryPath = path.url.joinAll(segments.getRange(2, segments.length));
162 } else {
163 logger.error('Unable to import `${element.source.uri.path}` from '
164 '${bootstrapId.path}.');
165 }
166
167 return "const LibraryIdentifier"
168 "(#${element.name}, $packageString, '$libraryPath')";
169 }
170
171 /// Builds a [String] representing an [ArgumentList] taking into account the
172 /// [libraryPrefixes].
173 String buildArgumentList(
174 ArgumentList args, InitializerPluginData pluginData) {
175 var buffer = new StringBuffer();
176 buffer.write('(');
177 var first = true;
178 for (var arg in args.arguments) {
179 if (!first) buffer.write(', ');
180 first = false;
181
182 Expression expression;
183 if (arg is NamedExpression) {
184 buffer.write('${arg.name.label.name}: ');
185 expression = arg.expression;
186 } else {
187 expression = arg;
188 }
189
190 buffer.write(buildExpression(expression, pluginData));
191 }
192 buffer.write(')');
193 return buffer.toString();
194 }
195
196 /// Builds a [String] representing an [Expression] taking into account the
197 /// [libraryPrefixes].
198 String buildExpression(
199 Expression expression, InitializerPluginData pluginData) {
200 var logger = pluginData.logger;
201 var libraryPrefixes = pluginData.libraryPrefixes;
202 var buffer = new StringBuffer();
203 if (expression is StringLiteral) {
204 var value = expression.stringValue;
205 if (value == null) {
206 logger.error('Only const strings are allowed in initializer '
207 'expressions, found $expression');
208 }
209 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'");
210 buffer.write("'$value'");
211 } else if (expression is BooleanLiteral ||
212 expression is DoubleLiteral ||
213 expression is IntegerLiteral ||
214 expression is NullLiteral) {
215 buffer.write('${expression}');
216 } else if (expression is ListLiteral) {
217 buffer.write('const [');
218 var first = true;
219 for (Expression listExpression in expression.elements) {
220 if (!first) buffer.write(', ');
221 first = false;
222 buffer.write(buildExpression(listExpression, pluginData));
223 }
224 buffer.write(']');
225 } else if (expression is MapLiteral) {
226 buffer.write('const {');
227 var first = true;
228 for (MapLiteralEntry entry in expression.entries) {
229 if (!first) buffer.write(', ');
230 first = false;
231 buffer.write(buildExpression(entry.key, pluginData));
232 buffer.write(': ');
233 buffer.write(buildExpression(entry.value, pluginData));
234 }
235 buffer.write('}');
236 } else if (expression is Identifier) {
237 var element = expression.bestElement;
238 if (element == null || !element.isPublic) {
239 logger.error('Private constants are not supported in intializer '
240 'constructors, found $element.');
241 }
242 libraryPrefixes.putIfAbsent(
243 element.library, () => 'i${libraryPrefixes.length}');
244
245 buffer.write('${libraryPrefixes[element.library]}.');
246 if (element is ClassElement) {
247 buffer.write(element.name);
248 } else if (element is PropertyAccessorElement) {
249 var variable = element.variable;
250 if (variable is FieldElement) {
251 buffer.write('${variable.enclosingElement.name}.');
252 }
253 buffer.write('${variable.name}');
254 } else {
255 logger.error('Unsupported argument to initializer constructor.');
256 }
257 } else {
258 logger.error('Only literals and identifiers are allowed for initializer '
259 'expressions, found $expression.');
260 }
261 return buffer.toString();
262 }
263 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698