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