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

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: wrapup 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
« no previous file with comments | « README.md ('k') | lib/plugin_transformer.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 // 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.targetElement;
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 [element] which
128 /// is an annotated class.
129 String buildClassTarget(
130 ClassElement element, InitializerPluginData pluginData) =>
131 buildSimpleTarget(element, pluginData);
132
133 /// Builds a [String] for the target of an [InitEntry] given [element] which
134 /// is an annotated function.
135 String buildFunctionTarget(
136 FunctionElement element, InitializerPluginData pluginData) =>
137 buildSimpleTarget(element, pluginData);
138
139 /// Builds a [String] for the target of an [InitEntry] for a simple [Element].
140 /// This is just the library prefix followed by the element name.
141 String buildSimpleTarget(Element element, InitializerPluginData pluginData) =>
142 '${pluginData.libraryPrefixes[element.library]}.${element.name}';
143
144 /// Builds a [String] for the target of an [InitEntry] given [element] which
145 /// is an annotated library.
146 String buildLibraryTarget(
147 LibraryElement element, InitializerPluginData pluginData) {
148 var bootstrapId = pluginData.bootstrapId;
149 var logger = pluginData.logger;
150 var segments = element.source.uri.pathSegments;
151 var package = segments[0];
152 var libraryPath;
153 var packageString;
154 if (bootstrapId.package == package &&
155 bootstrapId.path.startsWith('${segments[1]}/')) {
156 // reset `package` to null, we will do a relative path in this case.
157 packageString = 'null';
158 libraryPath = path.url.relative(
159 path.url.joinAll(segments.getRange(1, segments.length)),
160 from: path.url.dirname(path.url.join(bootstrapId.path)));
161 } else if (segments[1] == 'lib') {
162 packageString = "'$package'";
163 libraryPath = path.url.joinAll(segments.getRange(2, segments.length));
164 } else {
165 logger.error('Unable to import `${element.source.uri.path}` from '
166 '${bootstrapId.path}.');
167 }
168
169 return "const LibraryIdentifier"
170 "(#${element.name}, $packageString, '$libraryPath')";
171 }
172
173 /// Builds a [String] representing an [ArgumentList] taking into account the
174 /// [libraryPrefixes] from [pluginData].
175 String buildArgumentList(
176 ArgumentList args, InitializerPluginData pluginData) {
177 var buffer = new StringBuffer();
178 buffer.write('(');
179 var first = true;
180 for (var arg in args.arguments) {
181 if (!first) buffer.write(', ');
182 first = false;
183
184 Expression expression;
185 if (arg is NamedExpression) {
186 buffer.write('${arg.name.label.name}: ');
187 expression = arg.expression;
188 } else {
189 expression = arg;
190 }
191
192 buffer.write(buildExpression(expression, pluginData));
193 }
194 buffer.write(')');
195 return buffer.toString();
196 }
197
198 /// Builds a [String] representing [expression] taking into account the
199 /// [libraryPrefixes] from [pluginData].
200 String buildExpression(
201 Expression expression, InitializerPluginData pluginData) {
202 var logger = pluginData.logger;
203 var libraryPrefixes = pluginData.libraryPrefixes;
204 var buffer = new StringBuffer();
205 if (expression is StringLiteral) {
206 var value = expression.stringValue;
207 if (value == null) {
208 logger.error('Only const strings are allowed in initializer '
209 'expressions, found $expression');
210 }
211 value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'");
212 buffer.write("'$value'");
213 } else if (expression is BooleanLiteral ||
214 expression is DoubleLiteral ||
215 expression is IntegerLiteral ||
216 expression is NullLiteral) {
217 buffer.write('${expression}');
218 } else if (expression is ListLiteral) {
219 buffer.write('const [');
220 var first = true;
221 for (Expression listExpression in expression.elements) {
222 if (!first) buffer.write(', ');
223 first = false;
224 buffer.write(buildExpression(listExpression, pluginData));
225 }
226 buffer.write(']');
227 } else if (expression is MapLiteral) {
228 buffer.write('const {');
229 var first = true;
230 for (MapLiteralEntry entry in expression.entries) {
231 if (!first) buffer.write(', ');
232 first = false;
233 buffer.write(buildExpression(entry.key, pluginData));
234 buffer.write(': ');
235 buffer.write(buildExpression(entry.value, pluginData));
236 }
237 buffer.write('}');
238 } else if (expression is Identifier) {
239 var element = expression.bestElement;
240 if (element == null || !element.isPublic) {
241 logger.error('Private constants are not supported in intializer '
242 'constructors, found $element.');
243 }
244 libraryPrefixes.putIfAbsent(
245 element.library, () => 'i${libraryPrefixes.length}');
246
247 buffer.write('${libraryPrefixes[element.library]}.');
248 if (element is ClassElement) {
249 buffer.write(element.name);
250 } else if (element is PropertyAccessorElement) {
251 var variable = element.variable;
252 if (variable is FieldElement) {
253 buffer.write('${variable.enclosingElement.name}.');
254 }
255 buffer.write('${variable.name}');
256 } else {
257 logger.error('Unsupported argument to initializer constructor.');
258 }
259 } else {
260 logger.error('Only literals and identifiers are allowed for initializer '
261 'expressions, found $expression.');
262 }
263 return buffer.toString();
264 }
265 }
OLDNEW
« no previous file with comments | « README.md ('k') | lib/plugin_transformer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698