| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2016, 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 import 'dart:convert'; |
| 6 import 'dart:io'; |
| 7 import 'dart:mirrors'; |
| 8 |
| 9 import 'package:analyzer/src/generated/utilities_dart.dart'; |
| 10 import 'package:analyzer/src/summary/base.dart'; |
| 11 import 'package:analyzer/src/summary/format.dart'; |
| 12 import 'package:analyzer/src/summary/idl.dart'; |
| 13 import 'package:args/args.dart'; |
| 14 |
| 15 main(List<String> args) { |
| 16 ArgParser argParser = new ArgParser()..addFlag('raw'); |
| 17 ArgResults argResults = argParser.parse(args); |
| 18 if (argResults.rest.length != 1) { |
| 19 print(argParser.usage); |
| 20 exitCode = 1; |
| 21 return; |
| 22 } |
| 23 String path = argResults.rest[0]; |
| 24 List<int> bytes = new File(path).readAsBytesSync(); |
| 25 PackageBundle bundle = new PackageBundle.fromBuffer(bytes); |
| 26 SummaryInspector inspector = new SummaryInspector(argResults['raw']); |
| 27 print(inspector.dumpPackageBundle(bundle).join('\n')); |
| 28 } |
| 29 |
| 30 const int MAX_LINE_LENGTH = 80; |
| 31 |
| 32 /** |
| 33 * Cache used to speed up [isEnum]. |
| 34 */ |
| 35 Map<Type, bool> _isEnumCache = <Type, bool>{}; |
| 36 |
| 37 /** |
| 38 * Determine if the given [obj] has an enumerated type. |
| 39 */ |
| 40 bool isEnum(Object obj) { |
| 41 return _isEnumCache.putIfAbsent( |
| 42 obj.runtimeType, () => reflect(obj).type.isEnum); |
| 43 } |
| 44 |
| 45 /** |
| 46 * Decoded reprensentation of a part of a summary that occupies multiple lines |
| 47 * of output. |
| 48 */ |
| 49 class BrokenEntity implements DecodedEntity { |
| 50 final String opener; |
| 51 final Map<String, DecodedEntity> parts; |
| 52 final String closer; |
| 53 |
| 54 BrokenEntity(this.opener, this.parts, this.closer); |
| 55 |
| 56 @override |
| 57 List<String> getLines() { |
| 58 List<String> result = <String>[opener]; |
| 59 bool first = true; |
| 60 for (String key in parts.keys) { |
| 61 if (first) { |
| 62 first = false; |
| 63 } else { |
| 64 result[result.length - 1] += ','; |
| 65 } |
| 66 List<String> subResult = parts[key].getLines(); |
| 67 subResult[0] = '$key: ${subResult[0]}'; |
| 68 result.addAll(subResult.map((String s) => ' $s')); |
| 69 } |
| 70 result.add(closer); |
| 71 return result; |
| 72 } |
| 73 } |
| 74 |
| 75 /** |
| 76 * Decoded representation of a part of a summary. |
| 77 */ |
| 78 abstract class DecodedEntity { |
| 79 /** |
| 80 * Create a representation of a part of the summary that consists of a group |
| 81 * of entities (represented by [parts]) contained between [opener] and |
| 82 * [closer]. |
| 83 * |
| 84 * If [forceKeys] is `true`, the keys in [parts] will always be shown. If |
| 85 * [forceKeys] is `false`, they keys will only be shown if the output is |
| 86 * broken into multiple lines. |
| 87 */ |
| 88 factory DecodedEntity.group(String opener, Map<String, DecodedEntity> parts, |
| 89 String closer, bool forceKeys) { |
| 90 // Attempt to format the entity in a single line; if not bail out and |
| 91 // construct a _BrokenEntity. |
| 92 DecodedEntity bailout() => new BrokenEntity(opener, parts, closer); |
| 93 String short = opener; |
| 94 bool first = true; |
| 95 for (String key in parts.keys) { |
| 96 if (first) { |
| 97 first = false; |
| 98 } else { |
| 99 short += ', '; |
| 100 } |
| 101 DecodedEntity value = parts[key]; |
| 102 if (forceKeys) { |
| 103 short += '$key: '; |
| 104 } |
| 105 if (value is UnbrokenEntity) { |
| 106 short += value._s; |
| 107 } else { |
| 108 return bailout(); |
| 109 } |
| 110 if (short.length > MAX_LINE_LENGTH) { |
| 111 return bailout(); |
| 112 } |
| 113 } |
| 114 return new DecodedEntity.short(short + closer); |
| 115 } |
| 116 |
| 117 /** |
| 118 * Create a representation of a part of the summary that is represented by a |
| 119 * single unbroken string. |
| 120 */ |
| 121 factory DecodedEntity.short(String s) = UnbrokenEntity; |
| 122 |
| 123 /** |
| 124 * Format this entity into a sequence of strings (one per output line). |
| 125 */ |
| 126 List<String> getLines(); |
| 127 } |
| 128 |
| 129 /** |
| 130 * Wrapper around a [LinkedLibrary] and its constituent [UnlinkedUnit]s. |
| 131 */ |
| 132 class LibraryWrapper { |
| 133 final LinkedLibrary _linked; |
| 134 final List<UnlinkedUnit> _unlinked; |
| 135 |
| 136 LibraryWrapper(this._linked, this._unlinked); |
| 137 } |
| 138 |
| 139 /** |
| 140 * Wrapper around a [LinkedReference] and its corresponding [UnlinkedReference]. |
| 141 */ |
| 142 class ReferenceWrapper { |
| 143 final LinkedReference _linked; |
| 144 final UnlinkedReference _unlinked; |
| 145 |
| 146 ReferenceWrapper(this._linked, this._unlinked); |
| 147 |
| 148 String get name { |
| 149 if (_linked != null && _linked.name.isNotEmpty) { |
| 150 return _linked.name; |
| 151 } else if (_unlinked != null && _unlinked.name.isNotEmpty) { |
| 152 return _unlinked.name; |
| 153 } else { |
| 154 return '???'; |
| 155 } |
| 156 } |
| 157 } |
| 158 |
| 159 /** |
| 160 * Instances of [SummaryInspector] are capable of traversing a summary and |
| 161 * converting it to semi-human-readable output. |
| 162 */ |
| 163 class SummaryInspector { |
| 164 /** |
| 165 * The dependencies of the library currently being visited. |
| 166 */ |
| 167 List<LinkedDependency> _dependencies; |
| 168 |
| 169 /** |
| 170 * The references of the unit currently being visited. |
| 171 */ |
| 172 List<ReferenceWrapper> _references; |
| 173 |
| 174 /** |
| 175 * Indicates whether summary inspection should operate in "raw" mode. In this |
| 176 * mode, the structure of the summary file is not altered for easier |
| 177 * readability; everything is output in exactly the form in which it appears |
| 178 * in the file. |
| 179 */ |
| 180 final bool raw; |
| 181 |
| 182 SummaryInspector(this.raw); |
| 183 |
| 184 /** |
| 185 * Decode the object [obj], which was reached by examining [key] inside |
| 186 * another object. |
| 187 */ |
| 188 DecodedEntity decode(Object obj, String key) { |
| 189 if (!raw && obj is PackageBundle) { |
| 190 return decodePackageBundle(obj); |
| 191 } |
| 192 if (obj is LibraryWrapper) { |
| 193 return decodeLibrary(obj); |
| 194 } |
| 195 if (obj is UnitWrapper) { |
| 196 return decodeUnit(obj); |
| 197 } |
| 198 if (obj is ReferenceWrapper) { |
| 199 return decodeReference(obj); |
| 200 } |
| 201 if (obj is DecodedEntity) { |
| 202 return obj; |
| 203 } |
| 204 if (obj is SummaryClass) { |
| 205 Map<String, Object> map = obj.toMap(); |
| 206 return decodeMap(map); |
| 207 } else if (obj is List) { |
| 208 Map<String, DecodedEntity> parts = <String, DecodedEntity>{}; |
| 209 for (int i = 0; i < obj.length; i++) { |
| 210 parts[i.toString()] = decode(obj[i], key); |
| 211 } |
| 212 return new DecodedEntity.group('[', parts, ']', false); |
| 213 } else if (obj is String) { |
| 214 return new DecodedEntity.short(JSON.encode(obj)); |
| 215 } else if (isEnum(obj)) { |
| 216 return new DecodedEntity.short(obj.toString().split('.')[1]); |
| 217 } else if (obj is int && |
| 218 key == 'dependency' && |
| 219 _dependencies != null && |
| 220 obj < _dependencies.length) { |
| 221 return new DecodedEntity.short('$obj (${_dependencies[obj].uri})'); |
| 222 } else if (obj is int && |
| 223 key == 'reference' && |
| 224 _references != null && |
| 225 obj < _references.length) { |
| 226 return new DecodedEntity.short('$obj (${_references[obj].name})'); |
| 227 } else { |
| 228 return new DecodedEntity.short(obj.toString()); |
| 229 } |
| 230 } |
| 231 |
| 232 /** |
| 233 * Decode the given [LibraryWrapper]. |
| 234 */ |
| 235 DecodedEntity decodeLibrary(LibraryWrapper obj) { |
| 236 try { |
| 237 LinkedLibrary linked = obj._linked; |
| 238 List<UnlinkedUnit> unlinked = obj._unlinked; |
| 239 _dependencies = linked.dependencies; |
| 240 Map<String, Object> result = linked.toMap(); |
| 241 result.remove('units'); |
| 242 result['defining compilation unit'] = |
| 243 new UnitWrapper(linked.units[0], unlinked[0]); |
| 244 for (int i = 1; i < linked.units.length; i++) { |
| 245 String partUri = unlinked[0].publicNamespace.parts[i - 1]; |
| 246 result['part ${JSON.encode(partUri)}'] = |
| 247 new UnitWrapper(linked.units[i], unlinked[i]); |
| 248 } |
| 249 return decodeMap(result); |
| 250 } finally { |
| 251 _dependencies = null; |
| 252 } |
| 253 } |
| 254 |
| 255 /** |
| 256 * Decode the given [map]. |
| 257 */ |
| 258 DecodedEntity decodeMap(Map<String, Object> map) { |
| 259 Map<String, DecodedEntity> parts = <String, DecodedEntity>{}; |
| 260 map = reorderMap(map); |
| 261 map.forEach((String key, Object value) { |
| 262 if (value is String && value.isEmpty) { |
| 263 return; |
| 264 } |
| 265 if (isEnum(value) && (value as dynamic).index == 0) { |
| 266 return; |
| 267 } |
| 268 if (value is int && value == 0) { |
| 269 return; |
| 270 } |
| 271 if (value is bool && value == false) { |
| 272 return; |
| 273 } |
| 274 if (value == null) { |
| 275 return; |
| 276 } |
| 277 if (value is List) { |
| 278 if (value.isEmpty) { |
| 279 return; |
| 280 } |
| 281 DecodedEntity entity = decode(value, key); |
| 282 if (entity is BrokenEntity) { |
| 283 for (int i = 0; i < value.length; i++) { |
| 284 parts['$key[$i]'] = decode(value[i], key); |
| 285 } |
| 286 return; |
| 287 } else { |
| 288 parts[key] = entity; |
| 289 } |
| 290 } |
| 291 parts[key] = decode(value, key); |
| 292 }); |
| 293 return new DecodedEntity.group('{', parts, '}', true); |
| 294 } |
| 295 |
| 296 /** |
| 297 * Decode the given [PackageBundle]. |
| 298 */ |
| 299 DecodedEntity decodePackageBundle(PackageBundle bundle) { |
| 300 Map<String, UnlinkedUnit> units = <String, UnlinkedUnit>{}; |
| 301 Set<String> seenUnits = new Set<String>(); |
| 302 for (int i = 0; i < bundle.unlinkedUnits.length; i++) { |
| 303 units[bundle.unlinkedUnitUris[i]] = bundle.unlinkedUnits[i]; |
| 304 } |
| 305 Map<String, Object> restOfMap = bundle.toMap(); |
| 306 Map<String, Object> result = <String, Object>{}; |
| 307 result['version'] = new DecodedEntity.short( |
| 308 '${bundle.majorVersion}.${bundle.minorVersion}'); |
| 309 restOfMap.remove('majorVersion'); |
| 310 restOfMap.remove('minorVersion'); |
| 311 result['linkedLibraryUris'] = restOfMap['linkedLibraryUris']; |
| 312 result['unlinkedUnitUris'] = restOfMap['unlinkedUnitUris']; |
| 313 for (int i = 0; i < bundle.linkedLibraries.length; i++) { |
| 314 String libraryUriString = bundle.linkedLibraryUris[i]; |
| 315 Uri libraryUri = Uri.parse(libraryUriString); |
| 316 UnlinkedUnit unlinkedDefiningUnit = units[libraryUriString]; |
| 317 seenUnits.add(libraryUriString); |
| 318 List<UnlinkedUnit> libraryUnits = <UnlinkedUnit>[unlinkedDefiningUnit]; |
| 319 LinkedLibrary linkedLibrary = bundle.linkedLibraries[i]; |
| 320 for (int j = 1; j < linkedLibrary.units.length; j++) { |
| 321 String partUriString = resolveRelativeUri(libraryUri, |
| 322 Uri.parse(unlinkedDefiningUnit.publicNamespace.parts[j - 1])) |
| 323 .toString(); |
| 324 libraryUnits.add(units[partUriString]); |
| 325 seenUnits.add(partUriString); |
| 326 } |
| 327 result['library ${JSON.encode(libraryUriString)}'] = |
| 328 new LibraryWrapper(linkedLibrary, libraryUnits); |
| 329 } |
| 330 for (String uriString in units.keys) { |
| 331 if (seenUnits.contains(uriString)) { |
| 332 continue; |
| 333 } |
| 334 result['orphan unit ${JSON.encode(uriString)}'] = |
| 335 new UnitWrapper(null, units[uriString]); |
| 336 } |
| 337 restOfMap.remove('linkedLibraries'); |
| 338 restOfMap.remove('linkedLibraryUris'); |
| 339 restOfMap.remove('unlinkedUnits'); |
| 340 restOfMap.remove('unlinkedUnitUris'); |
| 341 result.addAll(restOfMap); |
| 342 return decodeMap(result); |
| 343 } |
| 344 |
| 345 /** |
| 346 * Decode the given [ReferenceWrapper]. |
| 347 */ |
| 348 DecodedEntity decodeReference(ReferenceWrapper obj) { |
| 349 Map<String, Object> result = obj._unlinked != null |
| 350 ? obj._unlinked.toMap() |
| 351 : <String, Object>{'linkedOnly': true}; |
| 352 if (obj._linked != null) { |
| 353 mergeMaps(result, obj._linked.toMap()); |
| 354 } |
| 355 return decodeMap(result); |
| 356 } |
| 357 |
| 358 /** |
| 359 * Decode the given [UnitWrapper]. |
| 360 */ |
| 361 DecodedEntity decodeUnit(UnitWrapper obj) { |
| 362 try { |
| 363 LinkedUnit linked = obj._linked; |
| 364 UnlinkedUnit unlinked = obj._unlinked ?? new UnlinkedUnitBuilder(); |
| 365 Map<String, Object> unlinkedMap = unlinked.toMap(); |
| 366 Map<String, Object> linkedMap = |
| 367 linked != null ? linked.toMap() : <String, Object>{}; |
| 368 Map<String, Object> result = <String, Object>{}; |
| 369 List<ReferenceWrapper> references = <ReferenceWrapper>[]; |
| 370 int numReferences = linked != null |
| 371 ? linked.references.length |
| 372 : unlinked.references.length; |
| 373 for (int i = 0; i < numReferences; i++) { |
| 374 references.add(new ReferenceWrapper( |
| 375 linked != null ? linked.references[i] : null, |
| 376 i < unlinked.references.length ? unlinked.references[i] : null)); |
| 377 } |
| 378 result['references'] = references; |
| 379 _references = references; |
| 380 unlinkedMap.remove('references'); |
| 381 linkedMap.remove('references'); |
| 382 linkedMap.forEach((String key, Object value) { |
| 383 result['linked $key'] = value; |
| 384 }); |
| 385 unlinkedMap.forEach((String key, Object value) { |
| 386 result[key] = value; |
| 387 }); |
| 388 return decodeMap(result); |
| 389 } finally { |
| 390 _references = null; |
| 391 } |
| 392 } |
| 393 |
| 394 /** |
| 395 * Decode the given [PackageBundle] and dump it to a list of strings. |
| 396 */ |
| 397 List<String> dumpPackageBundle(PackageBundle bundle) { |
| 398 DecodedEntity decoded = decode(bundle, 'PackageBundle'); |
| 399 return decoded.getLines(); |
| 400 } |
| 401 |
| 402 /** |
| 403 * Merge the contents of [other] into [result], discarding empty entries. |
| 404 */ |
| 405 void mergeMaps(Map<String, Object> result, Map<String, Object> other) { |
| 406 other.forEach((String key, Object value) { |
| 407 if (value is String && value.isEmpty) { |
| 408 return; |
| 409 } |
| 410 if (result.containsKey(key)) { |
| 411 Object oldValue = result[key]; |
| 412 if (oldValue is String && oldValue.isEmpty) { |
| 413 result[key] = value; |
| 414 } else { |
| 415 throw new Exception( |
| 416 'Duplicate values for $key: $oldValue and $value'); |
| 417 } |
| 418 } else { |
| 419 result[key] = value; |
| 420 } |
| 421 }); |
| 422 } |
| 423 |
| 424 /** |
| 425 * Reorder [map] for more intuitive display. |
| 426 */ |
| 427 Map<String, Object> reorderMap(Map<String, Object> map) { |
| 428 Map<String, Object> result = <String, Object>{}; |
| 429 if (map.containsKey('name')) { |
| 430 result['name'] = map['name']; |
| 431 } |
| 432 result.addAll(map); |
| 433 return result; |
| 434 } |
| 435 } |
| 436 |
| 437 /** |
| 438 * Decoded reprensentation of a part of a summary that occupies a single line of |
| 439 * output. |
| 440 */ |
| 441 class UnbrokenEntity implements DecodedEntity { |
| 442 final String _s; |
| 443 |
| 444 UnbrokenEntity(this._s); |
| 445 |
| 446 @override |
| 447 List<String> getLines() => <String>[_s]; |
| 448 } |
| 449 |
| 450 /** |
| 451 * Wrapper around a [LinkedUnit] and its corresponding [UnlinkedUnit]. |
| 452 */ |
| 453 class UnitWrapper { |
| 454 final LinkedUnit _linked; |
| 455 final UnlinkedUnit _unlinked; |
| 456 |
| 457 UnitWrapper(this._linked, this._unlinked); |
| 458 } |
| OLD | NEW |