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 | |
5 /// Holds a couple utility functions used at various places in the system. | |
6 | |
7 import 'dart:io'; | |
8 import 'package:path/path.dart' as path; | |
9 import 'package:analyzer/dart/ast/ast.dart' | |
10 show | |
11 ImportDirective, | |
12 ExportDirective, | |
13 PartDirective, | |
14 CompilationUnit, | |
15 Identifier, | |
16 AnnotatedNode, | |
17 AstNode, | |
18 Expression, | |
19 SimpleIdentifier, | |
20 MethodInvocation; | |
21 import 'package:analyzer/dart/element/element.dart'; | |
22 import 'package:analyzer/dart/element/type.dart'; | |
23 import 'package:analyzer/src/generated/constant.dart' show DartObject; | |
24 //TODO(leafp): Remove deprecated dependency | |
25 //ignore: DEPRECATED_MEMBER_USE | |
26 import 'package:analyzer/src/generated/element.dart' show DynamicTypeImpl; | |
27 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; | |
28 import 'package:analyzer/src/task/dart.dart' show ParseDartTask; | |
29 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; | |
30 import 'package:analyzer/src/generated/source.dart' show LineInfo, Source; | |
31 import 'package:analyzer/analyzer.dart' show parseDirectives; | |
32 import 'package:source_span/source_span.dart'; | |
33 | |
34 import 'codegen/js_names.dart' show invalidVariableName; | |
35 | |
36 bool isDartPrivateLibrary(LibraryElement library) { | |
37 var uri = library.source.uri; | |
38 if (uri.scheme != "dart") return false; | |
39 return Identifier.isPrivateName(uri.path); | |
40 } | |
41 | |
42 /// Choose a canonical name from the library element. This is safe to use as a | |
43 /// namespace in JS and Dart code generation. This never uses the library's | |
44 /// name (the identifier in the `library` declaration) as it doesn't have any | |
45 /// meaningful rules enforced. | |
46 String canonicalLibraryName(LibraryElement library) { | |
47 var uri = library.source.uri; | |
48 var name = path.basenameWithoutExtension(uri.pathSegments.last); | |
49 return _toIdentifier(name); | |
50 } | |
51 | |
52 /// Escape [name] to make it into a valid identifier. | |
53 String _toIdentifier(String name) { | |
54 if (name.length == 0) return r'$'; | |
55 | |
56 // Escape any invalid characters | |
57 StringBuffer buffer = null; | |
58 for (int i = 0; i < name.length; i++) { | |
59 var ch = name[i]; | |
60 var needsEscape = ch == r'$' || _invalidCharInIdentifier.hasMatch(ch); | |
61 if (needsEscape && buffer == null) { | |
62 buffer = new StringBuffer(name.substring(0, i)); | |
63 } | |
64 if (buffer != null) { | |
65 buffer.write(needsEscape ? '\$${ch.codeUnits.join("")}' : ch); | |
66 } | |
67 } | |
68 | |
69 var result = buffer != null ? '$buffer' : name; | |
70 // Ensure the identifier first character is not numeric and that the whole | |
71 // identifier is not a keyword. | |
72 if (result.startsWith(new RegExp('[0-9]')) || invalidVariableName(result)) { | |
73 return '\$$result'; | |
74 } | |
75 return result; | |
76 } | |
77 | |
78 // Invalid characters for identifiers, which would need to be escaped. | |
79 final _invalidCharInIdentifier = new RegExp(r'[^A-Za-z_$0-9]'); | |
80 | |
81 /// Returns all libraries transitively imported or exported from [start]. | |
82 List<LibraryElement> reachableLibraries(LibraryElement start) { | |
83 var results = <LibraryElement>[]; | |
84 var seen = new Set(); | |
85 void find(LibraryElement lib) { | |
86 if (seen.contains(lib)) return; | |
87 seen.add(lib); | |
88 results.add(lib); | |
89 lib.importedLibraries.forEach(find); | |
90 lib.exportedLibraries.forEach(find); | |
91 } | |
92 find(start); | |
93 return results; | |
94 } | |
95 | |
96 /// Returns all sources transitively imported or exported from [start] in | |
97 /// post-visit order. Internally this uses digest parsing to read only | |
98 /// directives from each source, that way library resolution can be done | |
99 /// bottom-up and improve performance of the analyzer internal cache. | |
100 Iterable<Source> reachableSources(Source start, AnalysisContext context) { | |
101 var results = <Source>[]; | |
102 var seen = new Set(); | |
103 void find(Source source) { | |
104 if (seen.contains(source)) return; | |
105 seen.add(source); | |
106 _importsAndExportsOf(source, context).forEach(find); | |
107 results.add(source); | |
108 } | |
109 find(start); | |
110 return results; | |
111 } | |
112 | |
113 /// Returns sources that are imported or exported in [source] (parts are | |
114 /// excluded). | |
115 Iterable<Source> _importsAndExportsOf(Source source, AnalysisContext context) { | |
116 var unit = | |
117 parseDirectives(context.getContents(source).data, name: source.fullName); | |
118 return unit.directives | |
119 .where((d) => d is ImportDirective || d is ExportDirective) | |
120 .map((d) { | |
121 var res = ParseDartTask.resolveDirective(context, source, d, null); | |
122 if (res == null) print('error: couldn\'t resolve $d'); | |
123 return res; | |
124 }).where((d) => d != null); | |
125 } | |
126 | |
127 /// Returns the enclosing library of [e]. | |
128 LibraryElement enclosingLibrary(Element e) { | |
129 while (e != null && e is! LibraryElement) e = e.enclosingElement; | |
130 return e; | |
131 } | |
132 | |
133 /// Returns sources that are included with part directives from [unit]. | |
134 Iterable<Source> partsOf(CompilationUnit unit, AnalysisContext context) { | |
135 return unit.directives.where((d) => d is PartDirective).map((d) { | |
136 var res = | |
137 ParseDartTask.resolveDirective(context, unit.element.source, d, null); | |
138 if (res == null) print('error: couldn\'t resolve $d'); | |
139 return res; | |
140 }).where((d) => d != null); | |
141 } | |
142 | |
143 /// Looks up the declaration that matches [member] in [type] or its superclasses | |
144 /// and interfaces, and returns its declared type. | |
145 // TODO(sigmund): add this to lookUp* in analyzer. The difference here is that | |
146 // we also look in interfaces in addition to superclasses. | |
147 FunctionType searchTypeFor(InterfaceType start, ExecutableElement member) { | |
148 var getMemberTypeHelper = _memberTypeGetter(member); | |
149 FunctionType search(InterfaceType type, bool first) { | |
150 if (type == null) return null; | |
151 var res = null; | |
152 if (!first) { | |
153 res = getMemberTypeHelper(type); | |
154 if (res != null) return res; | |
155 } | |
156 | |
157 for (var m in type.mixins.reversed) { | |
158 res = search(m, false); | |
159 if (res != null) return res; | |
160 } | |
161 | |
162 res = search(type.superclass, false); | |
163 if (res != null) return res; | |
164 | |
165 for (var i in type.interfaces) { | |
166 res = search(i, false); | |
167 if (res != null) return res; | |
168 } | |
169 | |
170 return null; | |
171 } | |
172 | |
173 return search(start, true); | |
174 } | |
175 | |
176 /// Looks up the declaration that matches [member] in [type] and returns it's | |
177 /// declared type. | |
178 FunctionType getMemberType(InterfaceType type, ExecutableElement member) => | |
179 _memberTypeGetter(member)(type); | |
180 | |
181 typedef FunctionType _MemberTypeGetter(InterfaceType type); | |
182 | |
183 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) { | |
184 String memberName = member.name; | |
185 final isGetter = member is PropertyAccessorElement && member.isGetter; | |
186 final isSetter = member is PropertyAccessorElement && member.isSetter; | |
187 | |
188 FunctionType f(InterfaceType type) { | |
189 ExecutableElement baseMethod; | |
190 try { | |
191 if (isGetter) { | |
192 assert(!isSetter); | |
193 // Look for getter or field. | |
194 baseMethod = type.getGetter(memberName); | |
195 } else if (isSetter) { | |
196 baseMethod = type.getSetter(memberName); | |
197 } else { | |
198 baseMethod = type.getMethod(memberName); | |
199 } | |
200 } catch (e) { | |
201 // TODO(sigmund): remove this try-catch block (see issue #48). | |
202 } | |
203 if (baseMethod == null || baseMethod.isStatic) return null; | |
204 return baseMethod.type; | |
205 } | |
206 ; | |
207 return f; | |
208 } | |
209 | |
210 bool isDynamicTarget(Expression node) { | |
211 if (node == null) return false; | |
212 | |
213 if (isLibraryPrefix(node)) return false; | |
214 | |
215 // Null type happens when we have unknown identifiers, like a dart: import | |
216 // that doesn't resolve. | |
217 var type = node.staticType; | |
218 return type == null || type.isDynamic; | |
219 } | |
220 | |
221 bool isLibraryPrefix(Expression node) => | |
222 node is SimpleIdentifier && node.staticElement is PrefixElement; | |
223 | |
224 /// Returns an ANSII color escape sequence corresponding to [levelName]. Colors | |
225 /// are defined for: severe, error, warning, or info. Returns null if the level | |
226 /// name is not recognized. | |
227 String colorOf(String levelName) { | |
228 levelName = levelName.toLowerCase(); | |
229 if (levelName == 'shout' || levelName == 'severe' || levelName == 'error') { | |
230 return _RED_COLOR; | |
231 } | |
232 if (levelName == 'warning') return _MAGENTA_COLOR; | |
233 if (levelName == 'info') return _CYAN_COLOR; | |
234 return null; | |
235 } | |
236 | |
237 const String _RED_COLOR = '\u001b[31m'; | |
238 const String _MAGENTA_COLOR = '\u001b[35m'; | |
239 const String _CYAN_COLOR = '\u001b[36m'; | |
240 const String GREEN_COLOR = '\u001b[32m'; | |
241 const String NO_COLOR = '\u001b[0m'; | |
242 | |
243 class OutWriter { | |
244 final String _path; | |
245 final StringBuffer _sb = new StringBuffer(); | |
246 int _indent = 0; | |
247 String _prefix = ""; | |
248 bool _needsIndent = true; | |
249 | |
250 OutWriter(this._path); | |
251 | |
252 void write(String string, [int indent = 0]) { | |
253 if (indent < 0) inc(indent); | |
254 | |
255 var lines = string.split('\n'); | |
256 for (var i = 0, end = lines.length - 1; i < end; i++) { | |
257 _writeln(lines[i]); | |
258 } | |
259 _write(lines.last); | |
260 | |
261 if (indent > 0) inc(indent); | |
262 } | |
263 | |
264 void _writeln(String string) { | |
265 if (_needsIndent && string.isNotEmpty) _sb.write(_prefix); | |
266 _sb.writeln(string); | |
267 _needsIndent = true; | |
268 } | |
269 | |
270 void _write(String string) { | |
271 if (_needsIndent && string.isNotEmpty) { | |
272 _sb.write(_prefix); | |
273 _needsIndent = false; | |
274 } | |
275 _sb.write(string); | |
276 } | |
277 | |
278 void inc([int n = 2]) { | |
279 _indent = _indent + n; | |
280 assert(_indent >= 0); | |
281 _prefix = "".padRight(_indent); | |
282 } | |
283 | |
284 void dec([int n = 2]) { | |
285 _indent = _indent - n; | |
286 assert(_indent >= 0); | |
287 _prefix = "".padRight(_indent); | |
288 } | |
289 | |
290 void close() { | |
291 new File(_path).writeAsStringSync('$_sb'); | |
292 } | |
293 } | |
294 | |
295 SourceLocation locationForOffset(LineInfo lineInfo, Uri uri, int offset) { | |
296 var loc = lineInfo.getLocation(offset); | |
297 return new SourceLocation(offset, | |
298 sourceUrl: uri, line: loc.lineNumber - 1, column: loc.columnNumber - 1); | |
299 } | |
300 | |
301 String resourceOutputPath(Uri resourceUri, Uri entryUri, String runtimeDir) { | |
302 if (resourceUri.scheme == 'package') return resourceUri.path; | |
303 | |
304 if (resourceUri.scheme != 'file') return null; | |
305 | |
306 var entryPath = entryUri.path; | |
307 // The entry uri is either a directory or a dart/html file. If the latter, | |
308 // trim the file. | |
309 var entryDir = entryPath.endsWith('.dart') || entryPath.endsWith('.html') | |
310 ? path.dirname(entryPath) | |
311 : entryPath; | |
312 var filepath = path.normalize(path.join(entryDir, resourceUri.path)); | |
313 if (path.isWithin(runtimeDir, filepath)) { | |
314 filepath = path.relative(filepath, from: runtimeDir); | |
315 return path.join('dev_compiler', 'runtime', filepath); | |
316 } | |
317 | |
318 return path.relative(resourceUri.path, from: entryDir); | |
319 } | |
320 | |
321 /// Given an annotated [node] and a [test] function, returns the first matching | |
322 /// constant valued annotation. | |
323 /// | |
324 /// For example if we had the ClassDeclaration node for `FontElement`: | |
325 /// | |
326 /// @js.JS('HTMLFontElement') | |
327 /// @deprecated | |
328 /// class FontElement { ... } | |
329 /// | |
330 /// We could match `@deprecated` with a test function like: | |
331 /// | |
332 /// (v) => v.type.name == 'Deprecated' && v.type.element.library.isDartCore | |
333 /// | |
334 DartObject findAnnotation(Element element, bool test(DartObject value)) { | |
335 for (var metadata in element.metadata) { | |
336 var value = metadata.constantValue; | |
337 if (value != null && test(value)) return value; | |
338 } | |
339 return null; | |
340 } | |
341 | |
342 /// Given a constant [value], a [fieldName], and an [expectedType], returns the | |
343 /// value of that field. | |
344 /// | |
345 /// If the field is missing or is not [expectedType], returns null. | |
346 DartObject getConstantField( | |
347 DartObject value, String fieldName, DartType expectedType) { | |
348 var f = value?.getField(fieldName); | |
349 return (f == null || f.type != expectedType) ? null : f; | |
350 } | |
351 | |
352 DartType fillDynamicTypeArgs(DartType t, TypeProvider types) { | |
353 if (t is ParameterizedType) { | |
354 var dyn = new List.filled(t.typeArguments.length, types.dynamicType); | |
355 return t.substitute2(dyn, t.typeArguments); | |
356 } | |
357 return t; | |
358 } | |
359 | |
360 /// Similar to [SimpleIdentifier] inGetterContext, inSetterContext, and | |
361 /// inDeclarationContext, this method returns true if [node] is used in an | |
362 /// invocation context such as a MethodInvocation. | |
363 bool inInvocationContext(SimpleIdentifier node) { | |
364 var parent = node.parent; | |
365 return parent is MethodInvocation && parent.methodName == node; | |
366 } | |
367 | |
368 // TODO(vsm): Move this onto the appropriate class. Ideally, we'd attach | |
369 // it to TypeProvider. | |
370 | |
371 /// Searches all supertype, in order of most derived members, to see if any | |
372 /// [match] a condition. If so, returns the first match, otherwise returns null. | |
373 InterfaceType findSupertype(InterfaceType type, bool match(InterfaceType t)) { | |
374 for (var m in type.mixins.reversed) { | |
375 if (match(m)) return m; | |
376 } | |
377 var s = type.superclass; | |
378 if (s == null) return null; | |
379 | |
380 if (match(s)) return type; | |
381 return findSupertype(s, match); | |
382 } | |
383 | |
384 SourceSpanWithContext createSpanHelper( | |
385 LineInfo lineInfo, int start, int end, Source source, String content) { | |
386 var startLoc = locationForOffset(lineInfo, source.uri, start); | |
387 var endLoc = locationForOffset(lineInfo, source.uri, end); | |
388 | |
389 var lineStart = startLoc.offset - startLoc.column; | |
390 // Find the end of the line. This is not exposed directly on LineInfo, but | |
391 // we can find it pretty easily. | |
392 // TODO(jmesserly): for now we do the simple linear scan. Ideally we can get | |
393 // some help from the LineInfo API. | |
394 int lineEnd = endLoc.offset; | |
395 int lineNum = lineInfo.getLocation(lineEnd).lineNumber; | |
396 while (lineEnd < content.length && | |
397 lineInfo.getLocation(++lineEnd).lineNumber == lineNum); | |
398 | |
399 var text = content.substring(start, end); | |
400 var lineText = content.substring(lineStart, lineEnd); | |
401 return new SourceSpanWithContext(startLoc, endLoc, text, lineText); | |
402 } | |
403 | |
404 bool isInlineJS(Element e) => | |
405 e is FunctionElement && | |
406 e.library.source.uri.toString() == 'dart:_foreign_helper' && | |
407 e.name == 'JS'; | |
408 | |
409 bool isDartMathMinMax(Element e) => | |
410 e is FunctionElement && | |
411 e.library.source.uri.toString() == 'dart:math' && | |
412 (e.name == 'min' || e.name == 'max'); | |
413 | |
414 /// Parses an enum value out of a string. | |
415 // TODO(ochafik): generic signature. | |
416 dynamic parseEnum(String s, List enumValues) => | |
417 enumValues.firstWhere((v) => s == getEnumName(v), | |
418 orElse: () => throw new ArgumentError('Unknown enum value: $s ' | |
419 '(expected one of ${enumValues.map(getEnumName)})')); | |
420 | |
421 /// Gets the "simple" name of an enum value. | |
422 getEnumName(v) { | |
423 var parts = '$v'.split('.'); | |
424 if (parts.length != 2 || !parts.every((p) => p.isNotEmpty)) { | |
425 throw new ArgumentError('Invalid enum value: $v'); | |
426 } | |
427 return parts[1]; | |
428 } | |
429 | |
430 class FileSystem { | |
431 const FileSystem(); | |
432 | |
433 void _ensureParentExists(String file) { | |
434 var dir = new Directory(path.dirname(file)); | |
435 if (!dir.existsSync()) dir.createSync(recursive: true); | |
436 } | |
437 | |
438 void copySync(String source, String destination) { | |
439 _ensureParentExists(destination); | |
440 new File(source).copySync(destination); | |
441 } | |
442 | |
443 void writeAsStringSync(String file, String contents) { | |
444 _ensureParentExists(file); | |
445 new File(file).writeAsStringSync(contents); | |
446 } | |
447 } | |
448 | |
449 //TODO(leafp): Is this really necessary? In theory I think | |
450 // the static type should always be filled in for resolved | |
451 // ASTs. This may be a vestigial workaround. | |
452 DartType getStaticType(Expression e) => | |
453 e.staticType ?? DynamicTypeImpl.instance; | |
454 | |
455 // TODO(leafp) Factor this out or use an existing library | |
456 class Tuple2<T0, T1> { | |
457 final T0 e0; | |
458 final T1 e1; | |
459 Tuple2(this.e0, this.e1); | |
460 } | |
OLD | NEW |