// 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:io'; import 'dart:mirrors'; import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:analyzer/src/summary/base.dart'; import 'package:analyzer/src/summary/format.dart'; import 'package:analyzer/src/summary/idl.dart'; import 'package:args/args.dart'; main(List args) { ArgParser argParser = new ArgParser()..addFlag('raw'); ArgResults argResults = argParser.parse(args); if (argResults.rest.length != 1) { print(argParser.usage); exitCode = 1; return; } String path = argResults.rest[0]; List bytes = new File(path).readAsBytesSync(); PackageBundle bundle = new PackageBundle.fromBuffer(bytes); SummaryInspector inspector = new SummaryInspector(argResults['raw']); print(inspector.dumpPackageBundle(bundle).join('\n')); } const int MAX_LINE_LENGTH = 80; /** * Cache used to speed up [isEnum]. */ Map _isEnumCache = {}; /** * 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 parts; final String closer; BrokenEntity(this.opener, this.parts, this.closer); @override List getLines() { List result = [opener]; bool first = true; for (String key in parts.keys) { if (first) { first = false; } else { result[result.length - 1] += ','; } List 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]) contained between [opener] and * [closer]. * * 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 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 getLines(); } /** * Wrapper around a [LinkedLibrary] and its constituent [UnlinkedUnit]s. */ class LibraryWrapper { final LinkedLibrary _linked; final List _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 _dependencies; /** * The references of the unit currently being visited. */ List _references; /** * Indicates whether summary inspection should operate in "raw" mode. In this * mode, the structure of the summary file is not altered for easier * readability; everything is output in exactly the form in which it appears * in the file. */ final bool raw; SummaryInspector(this.raw); /** * Decode the object [obj], which was reached by examining [key] inside * another object. */ DecodedEntity decode(Object obj, String key) { if (!raw && 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 map = obj.toMap(); return decodeMap(map); } else if (obj is List) { Map parts = {}; 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 unlinked = obj._unlinked; _dependencies = linked.dependencies; Map 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 map) { Map parts = {}; 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 units = {}; Set seenUnits = new Set(); for (int i = 0; i < bundle.unlinkedUnits.length; i++) { units[bundle.unlinkedUnitUris[i]] = bundle.unlinkedUnits[i]; } Map restOfMap = bundle.toMap(); Map result = {}; result['version'] = new DecodedEntity.short( '${bundle.majorVersion}.${bundle.minorVersion}'); restOfMap.remove('majorVersion'); restOfMap.remove('minorVersion'); result['linkedLibraryUris'] = restOfMap['linkedLibraryUris']; result['unlinkedUnitUris'] = restOfMap['unlinkedUnitUris']; 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 libraryUnits = [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 result = obj._unlinked != null ? obj._unlinked.toMap() : {'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 ?? new UnlinkedUnitBuilder(); Map unlinkedMap = unlinked.toMap(); Map linkedMap = linked != null ? linked.toMap() : {}; Map result = {}; List references = []; 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 dumpPackageBundle(PackageBundle bundle) { DecodedEntity decoded = decode(bundle, 'PackageBundle'); return decoded.getLines(); } /** * Merge the contents of [other] into [result], discarding empty entries. */ void mergeMaps(Map result, Map 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 reorderMap(Map map) { Map result = {}; 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 getLines() => [_s]; } /** * Wrapper around a [LinkedUnit] and its corresponding [UnlinkedUnit]. */ class UnitWrapper { final LinkedUnit _linked; final UnlinkedUnit _unlinked; UnitWrapper(this._linked, this._unlinked); }