blob: b968118fbaa24f54c1b27eb2bdadb80fa752c27c [file] [log] [blame]
// 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<String> 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<int> 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<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]) 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<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;
/**
* 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<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');
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<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 ?? new UnlinkedUnitBuilder();
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);
}