Chromium Code Reviews| Index: pkg/analyzer/lib/src/summary/inspect.dart |
| diff --git a/pkg/analyzer/lib/src/summary/inspect.dart b/pkg/analyzer/lib/src/summary/inspect.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a70fddd23b97d918bf445a1cbf8bbaca9f702f1c |
| --- /dev/null |
| +++ b/pkg/analyzer/lib/src/summary/inspect.dart |
| @@ -0,0 +1,428 @@ |
| +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +import 'dart:convert'; |
| +import 'dart:mirrors'; |
| + |
| +import 'package:analyzer/src/generated/utilities_dart.dart'; |
| +import 'package:analyzer/src/summary/base.dart'; |
| +import 'package:analyzer/src/summary/idl.dart'; |
| + |
| +const int MAX_LINE_LENGTH = 80; |
| + |
| +/** |
| + * Cache used to speed up [isEnum]. |
| + */ |
| +Map<Type, bool> _isEnumCache = <Type, bool>{}; |
| + |
| +/** |
| + * Determine if the given [obj] has an enumerated type. |
| + */ |
| +bool isEnum(Object obj) { |
| + return _isEnumCache.putIfAbsent( |
| + obj.runtimeType, () => reflect(obj).type.isEnum); |
| +} |
| + |
| +/** |
| + * Decoded reprensentation of a part of a summary that occupies multiple lines |
| + * of output. |
| + */ |
| +class BrokenEntity implements DecodedEntity { |
| + final String opener; |
| + final Map<String, DecodedEntity> parts; |
| + final String closer; |
| + |
| + BrokenEntity(this.opener, this.parts, this.closer); |
| + |
| + @override |
| + List<String> getLines() { |
| + List<String> result = <String>[opener]; |
| + bool first = true; |
| + for (String key in parts.keys) { |
| + if (first) { |
| + first = false; |
| + } else { |
| + result[result.length - 1] += ','; |
| + } |
| + List<String> subResult = parts[key].getLines(); |
| + subResult[0] = '$key: ${subResult[0]}'; |
| + result.addAll(subResult.map((String s) => ' $s')); |
| + } |
| + result.add(closer); |
| + return result; |
| + } |
| +} |
| + |
| +/** |
| + * Decoded representation of a part of a summary. |
| + */ |
| +abstract class DecodedEntity { |
| + /** |
| + * Create a representation of a part of the summary that consists of a group |
| + * of entities (represented by [parts]) contined between [opener] and |
|
scheglov
2016/04/21 22:11:41
contined -> contained
Paul Berry
2016/04/21 22:31:08
Done.
|
| + * [closers]. |
|
scheglov
2016/04/21 22:11:41
[closers] -> [closer]
Paul Berry
2016/04/21 22:31:08
Done.
|
| + * |
| + * If [forceKeys] is `true`, the keys in [parts] will always be shown. If |
| + * [forceKeys] is `false`, they keys will only be shown if the output is |
| + * broken into multiple lines. |
| + */ |
| + factory DecodedEntity.group(String opener, Map<String, DecodedEntity> parts, |
| + String closer, bool forceKeys) { |
| + // Attempt to format the entity in a single line; if not bail out and |
| + // construct a _BrokenEntity. |
| + DecodedEntity bailout() => new BrokenEntity(opener, parts, closer); |
| + String short = opener; |
| + bool first = true; |
| + for (String key in parts.keys) { |
| + if (first) { |
| + first = false; |
| + } else { |
| + short += ', '; |
| + } |
| + DecodedEntity value = parts[key]; |
| + if (forceKeys) { |
| + short += '$key: '; |
| + } |
| + if (value is UnbrokenEntity) { |
| + short += value._s; |
| + } else { |
| + return bailout(); |
| + } |
| + if (short.length > MAX_LINE_LENGTH) { |
| + return bailout(); |
| + } |
| + } |
| + return new DecodedEntity.short(short + closer); |
| + } |
| + |
| + /** |
| + * Create a representation of a part of the summary that is represented by a |
| + * single unbroken string. |
| + */ |
| + factory DecodedEntity.short(String s) = UnbrokenEntity; |
| + |
| + /** |
| + * Format this entity into a sequence of strings (one per output line). |
| + */ |
| + List<String> getLines(); |
| +} |
| + |
| +/** |
| + * Wrapper around a [LinkedLibrary] and its constituent [UnlinkedUnit]s. |
| + */ |
| +class LibraryWrapper { |
| + final LinkedLibrary _linked; |
| + final List<UnlinkedUnit> _unlinked; |
| + |
| + LibraryWrapper(this._linked, this._unlinked); |
| +} |
| + |
| +/** |
| + * Wrapper around a [LinkedReference] and its corresponding [UnlinkedReference]. |
| + */ |
| +class ReferenceWrapper { |
| + final LinkedReference _linked; |
| + final UnlinkedReference _unlinked; |
| + |
| + ReferenceWrapper(this._linked, this._unlinked); |
| + |
| + String get name { |
| + if (_linked != null && _linked.name.isNotEmpty) { |
| + return _linked.name; |
| + } else if (_unlinked != null && _unlinked.name.isNotEmpty) { |
| + return _unlinked.name; |
| + } else { |
| + return '???'; |
| + } |
| + } |
| +} |
| + |
| +/** |
| + * Instances of [SummaryInspector] are capable of traversing a summary and |
| + * converting it to semi-human-readable output. |
| + */ |
| +class SummaryInspector { |
| + /** |
| + * The dependencies of the library currently being visited. |
| + */ |
| + List<LinkedDependency> _dependencies; |
| + |
| + /** |
| + * The references of the unit currently being visited. |
| + */ |
| + List<ReferenceWrapper> _references; |
| + |
| + /** |
| + * Decode the object [obj], which was reached by examining [key] inside |
| + * another object. |
| + */ |
| + DecodedEntity decode(Object obj, String key) { |
| + if (obj is PackageBundle) { |
| + return decodePackageBundle(obj); |
| + } |
| + if (obj is LibraryWrapper) { |
| + return decodeLibrary(obj); |
| + } |
| + if (obj is UnitWrapper) { |
| + return decodeUnit(obj); |
| + } |
| + if (obj is ReferenceWrapper) { |
| + return decodeReference(obj); |
| + } |
| + if (obj is DecodedEntity) { |
| + return obj; |
| + } |
| + if (obj is SummaryClass) { |
| + Map<String, Object> map = obj.toMap(); |
| + return decodeMap(map); |
| + } else if (obj is List) { |
| + Map<String, DecodedEntity> parts = <String, DecodedEntity>{}; |
| + for (int i = 0; i < obj.length; i++) { |
| + parts[i.toString()] = decode(obj[i], key); |
| + } |
| + return new DecodedEntity.group('[', parts, ']', false); |
| + } else if (obj is String) { |
| + return new DecodedEntity.short(JSON.encode(obj)); |
| + } else if (isEnum(obj)) { |
| + return new DecodedEntity.short(obj.toString().split('.')[1]); |
| + } else if (obj is int && |
| + key == 'dependency' && |
| + _dependencies != null && |
| + obj < _dependencies.length) { |
| + return new DecodedEntity.short('$obj (${_dependencies[obj].uri})'); |
| + } else if (obj is int && |
| + key == 'reference' && |
| + _references != null && |
| + obj < _references.length) { |
| + return new DecodedEntity.short('$obj (${_references[obj].name})'); |
| + } else { |
| + return new DecodedEntity.short(obj.toString()); |
| + } |
| + } |
| + |
| + /** |
| + * Decode the given [LibraryWrapper]. |
| + */ |
| + DecodedEntity decodeLibrary(LibraryWrapper obj) { |
| + try { |
| + LinkedLibrary linked = obj._linked; |
| + List<UnlinkedUnit> unlinked = obj._unlinked; |
| + _dependencies = linked.dependencies; |
| + Map<String, Object> result = linked.toMap(); |
| + result.remove('units'); |
| + result['defining compilation unit'] = |
| + new UnitWrapper(linked.units[0], unlinked[0]); |
| + for (int i = 1; i < linked.units.length; i++) { |
| + String partUri = unlinked[0].publicNamespace.parts[i - 1]; |
| + result['part ${JSON.encode(partUri)}'] = |
| + new UnitWrapper(linked.units[i], unlinked[i]); |
| + } |
| + return decodeMap(result); |
| + } finally { |
| + _dependencies = null; |
| + } |
| + } |
| + |
| + /** |
| + * Decode the given [map]. |
| + */ |
| + DecodedEntity decodeMap(Map<String, Object> map) { |
| + Map<String, DecodedEntity> parts = <String, DecodedEntity>{}; |
| + map = reorderMap(map); |
| + map.forEach((String key, Object value) { |
| + if (value is String && value.isEmpty) { |
| + return; |
| + } |
| + if (isEnum(value) && (value as dynamic).index == 0) { |
| + return; |
| + } |
| + if (value is int && value == 0) { |
| + return; |
| + } |
| + if (value is bool && value == false) { |
| + return; |
| + } |
| + if (value == null) { |
| + return; |
| + } |
| + if (value is List) { |
| + if (value.isEmpty) { |
| + return; |
| + } |
| + DecodedEntity entity = decode(value, key); |
| + if (entity is BrokenEntity) { |
| + for (int i = 0; i < value.length; i++) { |
| + parts['$key[$i]'] = decode(value[i], key); |
| + } |
| + return; |
| + } else { |
| + parts[key] = entity; |
| + } |
| + } |
| + parts[key] = decode(value, key); |
| + }); |
| + return new DecodedEntity.group('{', parts, '}', true); |
| + } |
| + |
| + /** |
| + * Decode the given [PackageBundle]. |
| + */ |
| + DecodedEntity decodePackageBundle(PackageBundle bundle) { |
| + Map<String, UnlinkedUnit> units = <String, UnlinkedUnit>{}; |
| + Set<String> seenUnits = new Set<String>(); |
| + for (int i = 0; i < bundle.unlinkedUnits.length; i++) { |
| + units[bundle.unlinkedUnitUris[i]] = bundle.unlinkedUnits[i]; |
| + } |
| + Map<String, Object> restOfMap = bundle.toMap(); |
| + Map<String, Object> result = <String, Object>{}; |
| + result['version'] = new DecodedEntity.short( |
| + '${bundle.majorVersion}.${bundle.minorVersion}'); |
| + restOfMap.remove('majorVersion'); |
| + restOfMap.remove('minorVersion'); |
| + for (int i = 0; i < bundle.linkedLibraries.length; i++) { |
| + String libraryUriString = bundle.linkedLibraryUris[i]; |
| + Uri libraryUri = Uri.parse(libraryUriString); |
| + UnlinkedUnit unlinkedDefiningUnit = units[libraryUriString]; |
| + seenUnits.add(libraryUriString); |
| + List<UnlinkedUnit> libraryUnits = <UnlinkedUnit>[unlinkedDefiningUnit]; |
| + LinkedLibrary linkedLibrary = bundle.linkedLibraries[i]; |
| + for (int j = 1; j < linkedLibrary.units.length; j++) { |
| + String partUriString = resolveRelativeUri(libraryUri, |
| + Uri.parse(unlinkedDefiningUnit.publicNamespace.parts[j - 1])) |
| + .toString(); |
| + libraryUnits.add(units[partUriString]); |
| + seenUnits.add(partUriString); |
| + } |
| + result['library ${JSON.encode(libraryUriString)}'] = |
| + new LibraryWrapper(linkedLibrary, libraryUnits); |
| + } |
| + for (String uriString in units.keys) { |
| + if (seenUnits.contains(uriString)) { |
| + continue; |
| + } |
| + result['orphan unit ${JSON.encode(uriString)}'] = |
| + new UnitWrapper(null, units[uriString]); |
| + } |
| + restOfMap.remove('linkedLibraries'); |
| + restOfMap.remove('linkedLibraryUris'); |
| + restOfMap.remove('unlinkedUnits'); |
| + restOfMap.remove('unlinkedUnitUris'); |
| + result.addAll(restOfMap); |
| + return decodeMap(result); |
| + } |
| + |
| + /** |
| + * Decode the given [ReferenceWrapper]. |
| + */ |
| + DecodedEntity decodeReference(ReferenceWrapper obj) { |
| + Map<String, Object> result = obj._unlinked != null |
| + ? obj._unlinked.toMap() |
| + : <String, Object>{'linkedOnly': true}; |
| + if (obj._linked != null) { |
| + mergeMaps(result, obj._linked.toMap()); |
| + } |
| + return decodeMap(result); |
| + } |
| + |
| + /** |
| + * Decode the given [UnitWrapper]. |
| + */ |
| + DecodedEntity decodeUnit(UnitWrapper obj) { |
| + try { |
| + LinkedUnit linked = obj._linked; |
| + UnlinkedUnit unlinked = obj._unlinked; |
| + Map<String, Object> unlinkedMap = unlinked.toMap(); |
| + Map<String, Object> linkedMap = |
| + linked != null ? linked.toMap() : <String, Object>{}; |
| + Map<String, Object> result = <String, Object>{}; |
| + List<ReferenceWrapper> references = <ReferenceWrapper>[]; |
| + int numReferences = linked != null |
| + ? linked.references.length |
| + : unlinked.references.length; |
| + for (int i = 0; i < numReferences; i++) { |
| + references.add(new ReferenceWrapper( |
| + linked != null ? linked.references[i] : null, |
| + i < unlinked.references.length ? unlinked.references[i] : null)); |
| + } |
| + result['references'] = references; |
| + _references = references; |
| + unlinkedMap.remove('references'); |
| + linkedMap.remove('references'); |
| + linkedMap.forEach((String key, Object value) { |
| + result['linked $key'] = value; |
| + }); |
| + unlinkedMap.forEach((String key, Object value) { |
| + result[key] = value; |
| + }); |
| + return decodeMap(result); |
| + } finally { |
| + _references = null; |
| + } |
| + } |
| + |
| + /** |
| + * Decode the given [PackageBundle] and dump it to a list of strings. |
| + */ |
| + List<String> dumpPackageBundle(PackageBundle bundle) { |
| + DecodedEntity decoded = decode(bundle, 'PackageBundle'); |
| + return decoded.getLines(); |
| + } |
| + |
| + /** |
| + * Merge the contents of [other] into [result], discarding empty entries. |
| + */ |
| + void mergeMaps(Map<String, Object> result, Map<String, Object> other) { |
| + other.forEach((String key, Object value) { |
| + if (value is String && value.isEmpty) { |
| + return; |
| + } |
| + if (result.containsKey(key)) { |
| + Object oldValue = result[key]; |
| + if (oldValue is String && oldValue.isEmpty) { |
| + result[key] = value; |
| + } else { |
| + throw new Exception( |
| + 'Duplicate values for $key: $oldValue and $value'); |
| + } |
| + } else { |
| + result[key] = value; |
| + } |
| + }); |
| + } |
| + |
| + /** |
| + * Reorder [map] for more intuitive display. |
| + */ |
| + Map<String, Object> reorderMap(Map<String, Object> map) { |
| + Map<String, Object> result = <String, Object>{}; |
| + if (map.containsKey('name')) { |
| + result['name'] = map['name']; |
| + } |
| + result.addAll(map); |
| + return result; |
| + } |
| +} |
| + |
| +/** |
| + * Decoded reprensentation of a part of a summary that occupies a single line of |
| + * output. |
| + */ |
| +class UnbrokenEntity implements DecodedEntity { |
| + final String _s; |
| + |
| + UnbrokenEntity(this._s); |
| + |
| + @override |
| + List<String> getLines() => <String>[_s]; |
| +} |
| + |
| +/** |
| + * Wrapper around a [LinkedUnit] and its corresponding [UnlinkedUnit]. |
| + */ |
| +class UnitWrapper { |
| + final LinkedUnit _linked; |
| + final UnlinkedUnit _unlinked; |
| + |
| + UnitWrapper(this._linked, this._unlinked); |
| +} |