| 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 |