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

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

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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
« no previous file with comments | « html/pubspec.yaml ('k') | initialize/lib/build/loader_replacer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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.targetElement;
62 var elementAnnotation = pluginData.initializer.annotationElement;
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.targetNode;
82 var metaPrefix =
83 pluginData.libraryPrefixes[elementAnnotation.element.library];
84
85 var annotation = pluginData.initializer.annotationNode;
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 var args = buildArgumentList(annotation.arguments, pluginData);
96 return 'const $metaPrefix.${clazz}$constructor$args';
97 }
98
99 /// Builds a [String] representing the meta of an [InitEntry] given an
100 /// [ElementAnnotation] whose element was a [PropertyAccessorElement].
101 String buildPropertyMeta(
102 ElementAnnotation annotation, InitializerPluginData pluginData) {
103 var metaPrefix = pluginData.libraryPrefixes[annotation.element.library];
104 return '$metaPrefix.${annotation.element.name}';
105 }
106
107 /// Builds a [String] for the target of an [InitEntry] given an [Element] that
108 /// was annotated.
109 String buildTarget(InitializerPluginData pluginData) {
110 var element = pluginData.initializer.targetElement;
111 var logger = pluginData.logger;
112 if (element is LibraryElement) {
113 return buildLibraryTarget(element, pluginData);
114 } else if (element is ClassElement) {
115 return buildClassTarget(element, pluginData);
116 } else if (element is FunctionElement) {
117 return buildFunctionTarget(element, pluginData);
118 } else {
119 logger.error('Initializers can only be applied to top level functions, '
120 'libraries, and classes.');
121 }
122 return null;
123 }
124
125 /// Builds a [String] for the target of an [InitEntry] given [element] which
126 /// is an annotated class.
127 String buildClassTarget(
128 ClassElement element, InitializerPluginData pluginData) =>
129 buildSimpleTarget(element, pluginData);
130
131 /// Builds a [String] for the target of an [InitEntry] given [element] which
132 /// is an annotated function.
133 String buildFunctionTarget(
134 FunctionElement element, InitializerPluginData pluginData) =>
135 buildSimpleTarget(element, pluginData);
136
137 /// Builds a [String] for the target of an [InitEntry] for a simple [Element].
138 /// This is just the library prefix followed by the element name.
139 String buildSimpleTarget(Element element, InitializerPluginData pluginData) =>
140 '${pluginData.libraryPrefixes[element.library]}.${element.name}';
141
142 /// Builds a [String] for the target of an [InitEntry] given [element] which
143 /// is an annotated library.
144 String buildLibraryTarget(
145 LibraryElement 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] from [pluginData].
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 [expression] taking into account the
197 /// [libraryPrefixes] from [pluginData].
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 && expression.stringValue != null) {
204 buffer.write(_stringValue(expression.stringValue));
205 } else if (expression is BooleanLiteral ||
206 expression is DoubleLiteral ||
207 expression is IntegerLiteral ||
208 expression is NullLiteral) {
209 buffer.write('${expression}');
210 } else if (expression is ListLiteral) {
211 buffer.write('const [');
212 var first = true;
213 for (Expression listExpression in expression.elements) {
214 if (!first) buffer.write(', ');
215 first = false;
216 buffer.write(buildExpression(listExpression, pluginData));
217 }
218 buffer.write(']');
219 } else if (expression is MapLiteral) {
220 buffer.write('const {');
221 var first = true;
222 for (MapLiteralEntry entry in expression.entries) {
223 if (!first) buffer.write(', ');
224 first = false;
225 buffer.write(buildExpression(entry.key, pluginData));
226 buffer.write(': ');
227 buffer.write(buildExpression(entry.value, pluginData));
228 }
229 buffer.write('}');
230 } else if (expression is Identifier) {
231 var element = expression.bestElement;
232 if (element == null) {
233 logger.error('Unable to get `bestElement` for expression: $expression');
234 } else if (!element.isPublic) {
235 // Inline the evaluated value of private identifiers.
236 buffer.write(_evaluateExpression(expression, pluginData));
237 } else {
238 libraryPrefixes.putIfAbsent(
239 element.library, () => 'i${libraryPrefixes.length}');
240
241 buffer.write('${libraryPrefixes[element.library]}.');
242 if (element is ClassElement) {
243 buffer.write(element.name);
244 } else if (element is PropertyAccessorElement) {
245 var variable = element.variable;
246 if (variable is FieldElement) {
247 buffer.write('${variable.enclosingElement.name}.');
248 }
249 buffer.write('${variable.name}');
250 } else {
251 logger.error('Unsupported argument to initializer constructor.');
252 }
253 }
254 } else if (expression is PropertyAccess) {
255 buffer.write(buildExpression(expression.target, pluginData));
256 buffer.write('.${expression.propertyName}');
257 } else if (expression is InstanceCreationExpression) {
258 logger.error('Unsupported expression in initializer, found $expression. '
259 'Instance creation expressions are not supported (yet). Instead, '
260 'please assign it to a const variable and use that instead.');
261 } else {
262 buffer.write(_evaluateExpression(expression, pluginData));
263 }
264 return buffer.toString();
265 }
266
267 _evaluateExpression(Expression expression, InitializerPluginData pluginData) {
268 var logger = pluginData.logger;
269 var result = pluginData.resolver.evaluateConstant(
270 pluginData.initializer.targetElement.library, expression);
271 if (!result.isValid) {
272 logger.error('Invalid expression in initializer, found $expression. '
273 'And got the following errors: ${result.errors}.');
274 return null;
275 }
276 var value = result.value.value;
277 if (value == null) {
278 logger.error('Unsupported expression in initializer, found '
279 '$expression. Please file a bug at '
280 'https://github.com/dart-lang/initialize/issues');
281 return null;
282 }
283
284 if (value is String) value = _stringValue(value);
285 return value;
286 }
287
288 // Returns an expression for a string value. Wraps it in single quotes and
289 // escapes existing single quotes and escapes.
290 _stringValue(String value) {
291 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'");
292 return "'$value'";
293 }
294 }
OLDNEW
« no previous file with comments | « html/pubspec.yaml ('k') | initialize/lib/build/loader_replacer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698