OLD | NEW |
---|---|
(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 } | |
OLD | NEW |