08092d107c
Change-Id: I21a79df1900038fa4dbed97c085a64bdcf4b4156 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427580 Auto-Submit: Mayank Patke <fishythefish@google.com> Commit-Queue: Nate Biggs <natebiggs@google.com> Reviewed-by: Nate Biggs <natebiggs@google.com>
695 lines
22 KiB
Dart
695 lines
22 KiB
Dart
// Copyright (c) 2015, 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.
|
|
|
|
// ignore_for_file: avoid_dynamic_calls
|
|
|
|
/// Converters and codecs for converting between JSON and [Info] classes.
|
|
library;
|
|
|
|
import 'dart:collection';
|
|
import 'dart:convert';
|
|
|
|
import 'package:collection/collection.dart';
|
|
|
|
import 'info.dart';
|
|
import 'src/util.dart';
|
|
|
|
// TODO(sigmund): add unit tests.
|
|
class JsonToAllInfoConverter extends Converter<Map<String, dynamic>, AllInfo> {
|
|
// Using `MashMap` here because it's faster than the default `LinkedHashMap`.
|
|
final Map<String, Info> registry = HashMap<String, Info>();
|
|
|
|
@override
|
|
AllInfo convert(Map<String, dynamic> input) {
|
|
registry.clear();
|
|
|
|
var result = AllInfo();
|
|
var elements = input['elements'];
|
|
// TODO(srawlins): Since only the Map values are being extracted below,
|
|
// replace `as` with `cast` when `cast` becomes available in Dart 2.0:
|
|
//
|
|
// .addAll(elements['library'].values.cast<Map>().map(parseLibrary));
|
|
result.libraries.addAll(
|
|
(elements['library'] as Map).values.map((l) => parseLibrary(l)),
|
|
);
|
|
result.classes.addAll(
|
|
(elements['class'] as Map).values.map((c) => parseClass(c)),
|
|
);
|
|
result.classTypes.addAll(
|
|
(elements['classType'] as Map).values.map((c) => parseClassType(c)),
|
|
);
|
|
result.functions.addAll(
|
|
(elements['function'] as Map).values.map((f) => parseFunction(f)),
|
|
);
|
|
|
|
// TODO(het): Revert this when the dart2js with the new codec is in stable
|
|
if (elements['closure'] != null) {
|
|
result.closures.addAll(
|
|
(elements['closure'] as Map).values.map((c) => parseClosure(c)),
|
|
);
|
|
}
|
|
result.fields.addAll(
|
|
(elements['field'] as Map).values.map((f) => parseField(f)),
|
|
);
|
|
result.typedefs.addAll(
|
|
(elements['typedef'] as Map).values.map((t) => parseTypedef(t)),
|
|
);
|
|
result.constants.addAll(
|
|
(elements['constant'] as Map).values.map((c) => parseConstant(c)),
|
|
);
|
|
|
|
input['holding'].forEach((k, deps) {
|
|
final src = registry[k] as CodeInfo;
|
|
for (var dep in deps) {
|
|
final target = registry[dep['id']]!;
|
|
src.uses.add(DependencyInfo(target, dep['mask']));
|
|
}
|
|
});
|
|
|
|
input['dependencies']?.forEach((String k, dependencies) {
|
|
List<String> deps = dependencies;
|
|
result.dependencies[registry[k]!] = deps
|
|
.map((d) => registry[d]!)
|
|
.toList();
|
|
});
|
|
|
|
result.outputUnits.addAll(
|
|
(input['outputUnits'] as List).map((o) => parseOutputUnit(o)),
|
|
);
|
|
|
|
result.program = parseProgram(input['program']);
|
|
|
|
if (input['deferredFiles'] != null) {
|
|
final deferredFilesMap = (input['deferredFiles'] as Map)
|
|
.cast<String, Map<String, dynamic>>();
|
|
for (final library in deferredFilesMap.values) {
|
|
if (library['imports'] != null) {
|
|
// The importMap needs to be typed as <String, List<String>>, but the
|
|
// json parser produces <String, dynamic>.
|
|
final importMap = library['imports'] as Map<String, dynamic>;
|
|
importMap.forEach((prefix, files) {
|
|
importMap[prefix] = (files as List<dynamic>).cast<String>();
|
|
});
|
|
library['imports'] = importMap.cast<String, List<String>>();
|
|
}
|
|
}
|
|
result.deferredFiles = deferredFilesMap;
|
|
}
|
|
|
|
// todo: version, etc
|
|
return result;
|
|
}
|
|
|
|
OutputUnitInfo parseOutputUnit(Map json) {
|
|
final result = parseId(json['id']) as OutputUnitInfo;
|
|
result
|
|
..filename = json['filename']
|
|
..name = json['name']
|
|
..size = json['size'];
|
|
result.imports.addAll((json['imports'] as List).map((s) => s as String));
|
|
return result;
|
|
}
|
|
|
|
LibraryInfo parseLibrary(Map json) {
|
|
final result = parseId(json['id']) as LibraryInfo;
|
|
result
|
|
..name = json['name']
|
|
..uri = Uri.parse(json['canonicalUri'])
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?
|
|
..size = json['size'];
|
|
for (var child in json['children'].map((id) => parseId(id))) {
|
|
if (child is FunctionInfo) {
|
|
result.topLevelFunctions.add(child);
|
|
} else if (child is FieldInfo) {
|
|
result.topLevelVariables.add(child);
|
|
} else if (child is ClassInfo) {
|
|
result.classes.add(child);
|
|
} else if (child is ClassTypeInfo) {
|
|
result.classTypes.add(child);
|
|
} else if (child is TypedefInfo) {
|
|
result.typedefs.add(child);
|
|
} else {
|
|
throw StateError('Invalid LibraryInfo child: $child');
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ClassInfo parseClass(Map json) {
|
|
final result = parseId(json['id']) as ClassInfo;
|
|
result
|
|
..name = json['name']
|
|
..parent = parseId(json['parent'])
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?
|
|
..size = json['size']
|
|
..isAbstract = json['modifiers']['abstract'] == true;
|
|
for (var child in json['children'].map((id) => parseId(id))) {
|
|
if (child is FunctionInfo) {
|
|
result.functions.add(child);
|
|
} else if (child is FieldInfo) {
|
|
result.fields.add(child);
|
|
} else {
|
|
throw StateError('Invalid ClassInfo child: $child');
|
|
}
|
|
}
|
|
result.supers.addAll(
|
|
json['supers'].map<ClassInfo>((id) => parseId(id) as ClassInfo),
|
|
);
|
|
return result;
|
|
}
|
|
|
|
ClassTypeInfo parseClassType(Map json) {
|
|
final result = parseId(json['id']) as ClassTypeInfo;
|
|
result
|
|
..name = json['name']
|
|
..parent = parseId(json['parent'])
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?
|
|
..size = json['size'];
|
|
return result;
|
|
}
|
|
|
|
FieldInfo parseField(Map json) {
|
|
final result = parseId(json['id']) as FieldInfo;
|
|
return result
|
|
..name = json['name']
|
|
..parent = parseId(json['parent'])
|
|
..coverageId = json['coverageId']
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?
|
|
..size = json['size']
|
|
..type = json['type']
|
|
..inferredType = json['inferredType']
|
|
..code = parseCode(json['code'])
|
|
..isConst = json['const'] ?? false
|
|
..initializer = parseId(json['initializer']) as ConstantInfo?
|
|
..closures = (json['children'] as List)
|
|
.map<ClosureInfo>((c) => parseId(c) as ClosureInfo)
|
|
.toList();
|
|
}
|
|
|
|
ConstantInfo parseConstant(Map json) {
|
|
final result = parseId(json['id']) as ConstantInfo;
|
|
return result
|
|
..name = json['name']
|
|
..code = parseCode(json['code'])
|
|
..size = json['size']
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?;
|
|
}
|
|
|
|
TypedefInfo parseTypedef(Map json) {
|
|
final result = parseId(json['id']) as TypedefInfo;
|
|
return result
|
|
..name = json['name']
|
|
..parent = parseId(json['parent'])
|
|
..type = json['type']
|
|
..size = 0;
|
|
}
|
|
|
|
ProgramInfo parseProgram(Map json) {
|
|
// TODO(het): Revert this when the dart2js with the new codec is in stable
|
|
final compilationDuration = json['compilationDuration'];
|
|
final compilationDurationParsed = compilationDuration is String
|
|
? _parseDuration(compilationDuration)
|
|
: Duration(microseconds: compilationDuration as int);
|
|
|
|
final toJsonDuration = json['toJsonDuration'];
|
|
final toJsonDurationParsed = toJsonDuration is String
|
|
? _parseDuration(toJsonDuration)
|
|
: Duration(microseconds: toJsonDuration as int);
|
|
|
|
final dumpInfoDuration = json['dumpInfoDuration'];
|
|
final dumpInfoDurationParsed = dumpInfoDuration is String
|
|
? _parseDuration(dumpInfoDuration)
|
|
: Duration(microseconds: dumpInfoDuration as int);
|
|
|
|
final programInfo = ProgramInfo(
|
|
entrypoint: parseId(json['entrypoint']) as FunctionInfo,
|
|
size: json['size'],
|
|
ramUsage: json['ramUsage'],
|
|
compilationMoment: DateTime.parse(json['compilationMoment']),
|
|
dart2jsVersion: json['dart2jsVersion'],
|
|
noSuchMethodEnabled: json['noSuchMethodEnabled'],
|
|
isRuntimeTypeUsed: json['isRuntimeTypeUsed'],
|
|
isIsolateInUse: json['isIsolateInUse'],
|
|
isFunctionApplyUsed: json['isFunctionApplyUsed'],
|
|
isMirrorsUsed: json['isMirrorsUsed'],
|
|
minified: json['minified'],
|
|
compilationDuration: compilationDurationParsed,
|
|
toJsonDuration: toJsonDurationParsed,
|
|
dumpInfoDuration: dumpInfoDurationParsed,
|
|
);
|
|
|
|
return programInfo;
|
|
}
|
|
|
|
/// Parse a string formatted as "XX:YY:ZZ.ZZZZZ" into a [Duration].
|
|
Duration _parseDuration(String duration) {
|
|
if (!duration.contains(':')) {
|
|
return Duration(milliseconds: int.parse(duration));
|
|
}
|
|
var parts = duration.split(':');
|
|
var hours = double.parse(parts[0]);
|
|
var minutes = double.parse(parts[1]);
|
|
var seconds = double.parse(parts[2]);
|
|
const secondsInMillis = 1000;
|
|
const minutesInMillis = 60 * secondsInMillis;
|
|
const hoursInMillis = 60 * minutesInMillis;
|
|
var totalMillis =
|
|
secondsInMillis * seconds +
|
|
minutesInMillis * minutes +
|
|
hoursInMillis * hours;
|
|
return Duration(milliseconds: totalMillis.round());
|
|
}
|
|
|
|
FunctionInfo parseFunction(Map json) {
|
|
final result = parseId(json['id']) as FunctionInfo;
|
|
return result
|
|
..name = json['name']
|
|
..parent = parseId(json['parent'])
|
|
..coverageId = json['coverageId']
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?
|
|
..size = json['size']
|
|
..functionKind = json['functionKind']
|
|
..type = json['type']
|
|
..returnType = json['returnType']
|
|
..inferredReturnType = json['inferredReturnType']
|
|
..parameters = (json['parameters'] as List)
|
|
.map((p) => parseParameter(p))
|
|
.toList()
|
|
..code = parseCode(json['code'])
|
|
..sideEffects = json['sideEffects']
|
|
..inlinedCount = json['inlinedCount']
|
|
..modifiers = parseModifiers(Map<String, bool>.from(json['modifiers']))
|
|
..closures = (json['children'] as List)
|
|
.map<ClosureInfo>((c) => parseId(c) as ClosureInfo)
|
|
.toList();
|
|
}
|
|
|
|
ParameterInfo parseParameter(Map json) =>
|
|
ParameterInfo(json['name'], json['type'], json['declaredType']);
|
|
|
|
FunctionModifiers parseModifiers(Map<String, bool> json) {
|
|
return FunctionModifiers(
|
|
isStatic: json['static'] == true,
|
|
isConst: json['const'] == true,
|
|
isFactory: json['factory'] == true,
|
|
isExternal: json['external'] == true,
|
|
);
|
|
}
|
|
|
|
ClosureInfo parseClosure(Map json) {
|
|
final result = parseId(json['id']) as ClosureInfo;
|
|
return result
|
|
..name = json['name']
|
|
..parent = parseId(json['parent'])
|
|
..outputUnit = parseId(json['outputUnit']) as OutputUnitInfo?
|
|
..size = json['size']
|
|
..function = parseId(json['function']) as FunctionInfo;
|
|
}
|
|
|
|
Info? parseId(String? serializedId) {
|
|
if (serializedId == null) {
|
|
return null;
|
|
}
|
|
return registry.putIfAbsent(serializedId, () {
|
|
if (serializedId.startsWith('function/')) {
|
|
return FunctionInfo.internal();
|
|
} else if (serializedId.startsWith('closure/')) {
|
|
return ClosureInfo.internal();
|
|
} else if (serializedId.startsWith('library/')) {
|
|
return LibraryInfo.internal();
|
|
} else if (serializedId.startsWith('class/')) {
|
|
return ClassInfo.internal();
|
|
} else if (serializedId.startsWith('classType/')) {
|
|
return ClassTypeInfo.internal();
|
|
} else if (serializedId.startsWith('field/')) {
|
|
return FieldInfo.internal();
|
|
} else if (serializedId.startsWith('constant/')) {
|
|
return ConstantInfo.internal();
|
|
} else if (serializedId.startsWith('typedef/')) {
|
|
return TypedefInfo.internal();
|
|
} else if (serializedId.startsWith('outputUnit/')) {
|
|
return OutputUnitInfo.internal();
|
|
}
|
|
throw StateError('Invalid serialized ID found: $serializedId');
|
|
});
|
|
}
|
|
|
|
List<CodeSpan> parseCode(dynamic json) {
|
|
// backwards compatibility with format 5.1:
|
|
if (json is String) {
|
|
return [CodeSpan(start: null, end: null, text: json)];
|
|
}
|
|
|
|
if (json is List) {
|
|
return json.map((dynamic value) {
|
|
Map<String, dynamic> jsonCode = value;
|
|
return CodeSpan(
|
|
start: jsonCode['start'],
|
|
end: jsonCode['end'],
|
|
text: jsonCode['text'],
|
|
);
|
|
}).toList();
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
class AllInfoToJsonConverter extends Converter<AllInfo, Map>
|
|
implements InfoVisitor<Map> {
|
|
/// Whether to generate json compatible with format 5.1
|
|
final bool isBackwardCompatible;
|
|
|
|
final Map<Info, Id> ids = HashMap<Info, Id>();
|
|
final Map<String, int> idCounter = <String, int>{};
|
|
|
|
AllInfoToJsonConverter({this.isBackwardCompatible = false});
|
|
|
|
Id idFor(Info info) {
|
|
var serializedId = ids[info];
|
|
if (serializedId != null) return serializedId;
|
|
|
|
assert(
|
|
info is LibraryInfo ||
|
|
info is ConstantInfo ||
|
|
info is OutputUnitInfo ||
|
|
info is ClassInfo ||
|
|
info.parent != null,
|
|
"$info",
|
|
);
|
|
|
|
String name;
|
|
if (info is ConstantInfo) {
|
|
// No name and no parent, so `longName` isn't helpful
|
|
assert(info.name.isEmpty);
|
|
assert(info.parent == null);
|
|
// Instead, use the content of the code.
|
|
name = info.code.first.text ?? '';
|
|
} else {
|
|
name = longName(info, useLibraryUri: true, forId: true);
|
|
}
|
|
|
|
// longName isn't guaranteed to create unique serializedIds for some info
|
|
// constructs (such as closures), so we disambiguate here.
|
|
Id id = Id(info.kind, name);
|
|
final count = idCounter.update(
|
|
id.serializedId,
|
|
(v) => v + 1,
|
|
ifAbsent: () => 0,
|
|
);
|
|
if (count > 0) {
|
|
id = Id(info.kind, '$name%${count - 1}');
|
|
}
|
|
return ids[info] = id;
|
|
}
|
|
|
|
@override
|
|
Map convert(AllInfo input) => input.accept(this);
|
|
|
|
Map _visitList(List<Info> infos) {
|
|
// Using SplayTree to maintain a consistent order of keys
|
|
var map = SplayTreeMap<String, Map>(compareNatural);
|
|
for (var info in infos) {
|
|
map[idFor(info).id] = info.accept(this);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
Map _visitAllInfoElements(AllInfo info) {
|
|
var jsonLibraries = _visitList(info.libraries);
|
|
var jsonClasses = _visitList(info.classes);
|
|
var jsonClassTypes = _visitList(info.classTypes);
|
|
var jsonFunctions = _visitList(info.functions);
|
|
var jsonTypedefs = _visitList(info.typedefs);
|
|
var jsonFields = _visitList(info.fields);
|
|
var jsonConstants = _visitList(info.constants);
|
|
var jsonClosures = _visitList(info.closures);
|
|
return {
|
|
'library': jsonLibraries,
|
|
'class': jsonClasses,
|
|
'classType': jsonClassTypes,
|
|
'function': jsonFunctions,
|
|
'typedef': jsonTypedefs,
|
|
'field': jsonFields,
|
|
'constant': jsonConstants,
|
|
'closure': jsonClosures,
|
|
};
|
|
}
|
|
|
|
Map visitDependencyInfo(DependencyInfo info) =>
|
|
_createFromDepInfo(info).toJson();
|
|
|
|
_InfoClass _createFromDepInfo(DependencyInfo info) =>
|
|
_InfoClass._(idFor(info.target).serializedId, info.mask);
|
|
|
|
Map _visitAllInfoHolding(AllInfo allInfo) {
|
|
var map = SplayTreeMap<String, List<Map<String, dynamic>>>(compareNatural);
|
|
void helper(CodeInfo info) {
|
|
if (info.uses.isEmpty) return;
|
|
map[idFor(info).serializedId] = SplayTreeSet.of(
|
|
info.uses.map(_createFromDepInfo),
|
|
).map((e) => e.toJson()).toList();
|
|
}
|
|
|
|
allInfo.functions.forEach(helper);
|
|
allInfo.fields.forEach(helper);
|
|
return map;
|
|
}
|
|
|
|
Map _visitAllInfoDependencies(AllInfo allInfo) {
|
|
var map = SplayTreeMap<String, List>(compareNatural);
|
|
allInfo.dependencies.forEach((k, v) {
|
|
map[idFor(k).serializedId] = _toSortedSerializedIds(v, idFor);
|
|
});
|
|
return map;
|
|
}
|
|
|
|
@override
|
|
Map visitAll(AllInfo info) {
|
|
var elements = _visitAllInfoElements(info);
|
|
var jsonHolding = _visitAllInfoHolding(info);
|
|
var jsonDependencies = _visitAllInfoDependencies(info);
|
|
return {
|
|
'dump_version': isBackwardCompatible ? 5 : info.version,
|
|
'dump_minor_version': isBackwardCompatible ? 1 : info.minorVersion,
|
|
'program': info.program!.accept(this),
|
|
'elements': elements,
|
|
'holding': jsonHolding,
|
|
'dependencies': jsonDependencies,
|
|
'outputUnits': info.outputUnits.map((u) => u.accept(this)).toList(),
|
|
'deferredFiles': info.deferredFiles,
|
|
};
|
|
}
|
|
|
|
@override
|
|
Map visitProgram(ProgramInfo info) {
|
|
return {
|
|
'entrypoint': idFor(info.entrypoint).serializedId,
|
|
'size': info.size,
|
|
'ramUsage': info.ramUsage,
|
|
'dart2jsVersion': info.dart2jsVersion,
|
|
'compilationMoment': '${info.compilationMoment}',
|
|
'compilationDuration': info.compilationDuration.inMicroseconds,
|
|
'toJsonDuration': info.toJsonDuration.inMicroseconds,
|
|
'dumpInfoDuration': info.dumpInfoDuration.inMicroseconds,
|
|
'noSuchMethodEnabled': info.noSuchMethodEnabled,
|
|
'isRuntimeTypeUsed': info.isRuntimeTypeUsed,
|
|
'isIsolateInUse': info.isIsolateInUse,
|
|
'isFunctionApplyUsed': info.isFunctionApplyUsed,
|
|
'isMirrorsUsed': info.isMirrorsUsed,
|
|
'minified': info.minified,
|
|
};
|
|
}
|
|
|
|
Map _visitBasicInfo(BasicInfo info) {
|
|
var res = {
|
|
'id': idFor(info).serializedId,
|
|
'kind': kindToString(info.kind),
|
|
'name': info.name,
|
|
'size': info.size,
|
|
};
|
|
// TODO(sigmund): Omit this also when outputUnit.id == 0 (most code is in
|
|
// the main output unit by default).
|
|
if (info.outputUnit != null) {
|
|
res['outputUnit'] = idFor(info.outputUnit!).serializedId;
|
|
}
|
|
if (info.coverageId != null) res['coverageId'] = info.coverageId;
|
|
if (info.parent != null) res['parent'] = idFor(info.parent!).serializedId;
|
|
return res;
|
|
}
|
|
|
|
@override
|
|
Map visitLibrary(LibraryInfo info) {
|
|
return _visitBasicInfo(info)..addAll(<String, Object>{
|
|
'children': _toSortedSerializedIds([
|
|
...info.topLevelFunctions,
|
|
...info.topLevelVariables,
|
|
...info.classes,
|
|
...info.classTypes,
|
|
...info.typedefs,
|
|
], idFor),
|
|
'canonicalUri': '${info.uri}',
|
|
});
|
|
}
|
|
|
|
@override
|
|
Map visitClass(ClassInfo info) {
|
|
return _visitBasicInfo(info)..addAll(<String, Object>{
|
|
// TODO(sigmund): change format, include only when abstract is true.
|
|
'modifiers': {'abstract': info.isAbstract},
|
|
'children': _toSortedSerializedIds([
|
|
...info.fields,
|
|
...info.functions,
|
|
], idFor),
|
|
'supers': _toSortedSerializedIds(info.supers, idFor),
|
|
});
|
|
}
|
|
|
|
@override
|
|
Map visitClassType(ClassTypeInfo info) {
|
|
return _visitBasicInfo(info);
|
|
}
|
|
|
|
@override
|
|
Map visitField(FieldInfo info) {
|
|
var result = _visitBasicInfo(info)
|
|
..addAll(<String, Object>{
|
|
'children': _toSortedSerializedIds(info.closures, idFor),
|
|
'inferredType': info.inferredType,
|
|
'code': _serializeCode(info.code),
|
|
'type': info.type,
|
|
});
|
|
if (info.isConst) {
|
|
result['const'] = true;
|
|
if (info.initializer != null) {
|
|
result['initializer'] = idFor(info.initializer!).serializedId;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
Map visitConstant(ConstantInfo info) =>
|
|
_visitBasicInfo(info)
|
|
..addAll(<String, Object>{'code': _serializeCode(info.code)});
|
|
|
|
// TODO(sigmund): exclude false values (requires bumping the format version):
|
|
// var res = <String, bool>{};
|
|
// if (isStatic) res['static'] = true;
|
|
// if (isConst) res['const'] = true;
|
|
// if (isFactory) res['factory'] = true;
|
|
// if (isExternal) res['external'] = true;
|
|
// return res;
|
|
Map _visitFunctionModifiers(FunctionModifiers mods) => {
|
|
'static': mods.isStatic,
|
|
'const': mods.isConst,
|
|
'factory': mods.isFactory,
|
|
'external': mods.isExternal,
|
|
};
|
|
|
|
Map _visitParameterInfo(ParameterInfo info) => {
|
|
'name': info.name,
|
|
'type': info.type,
|
|
'declaredType': info.declaredType,
|
|
};
|
|
|
|
@override
|
|
Map visitFunction(FunctionInfo info) {
|
|
return _visitBasicInfo(info)..addAll(<String, Object?>{
|
|
'children': _toSortedSerializedIds(info.closures, idFor),
|
|
'modifiers': _visitFunctionModifiers(info.modifiers),
|
|
'returnType': info.returnType,
|
|
'inferredReturnType': info.inferredReturnType,
|
|
'parameters': info.parameters.map((p) => _visitParameterInfo(p)).toList(),
|
|
'sideEffects': info.sideEffects,
|
|
'inlinedCount': info.inlinedCount,
|
|
'code': _serializeCode(info.code),
|
|
'type': info.type,
|
|
'functionKind': info.functionKind,
|
|
// Note: version 3.2 of dump-info serializes `uses` in a section called
|
|
// `holding` at the top-level.
|
|
});
|
|
}
|
|
|
|
@override
|
|
Map visitClosure(ClosureInfo info) {
|
|
return _visitBasicInfo(info)
|
|
..addAll(<String, Object>{'function': idFor(info.function).serializedId});
|
|
}
|
|
|
|
@override
|
|
Map visitTypedef(TypedefInfo info) =>
|
|
_visitBasicInfo(info)..['type'] = info.type;
|
|
|
|
@override
|
|
Map visitOutput(OutputUnitInfo info) => _visitBasicInfo(info)
|
|
..['filename'] = info.filename
|
|
..['imports'] = info.imports;
|
|
|
|
Object _serializeCode(List<CodeSpan> code) {
|
|
if (isBackwardCompatible) {
|
|
return code.map((c) => c.text).join('\n');
|
|
}
|
|
return code
|
|
.map<Object>((c) => {'start': c.start, 'end': c.end, 'text': c.text})
|
|
.toList();
|
|
}
|
|
|
|
List<String> _toSortedSerializedIds(
|
|
Iterable<Info> infos,
|
|
Id Function(Info) getId,
|
|
) => infos.map((i) => getId(i).serializedId).toList()..sort(compareNatural);
|
|
}
|
|
|
|
class AllInfoJsonCodec extends Codec<AllInfo, Map> {
|
|
@override
|
|
final Converter<AllInfo, Map> encoder;
|
|
@override
|
|
final Converter<Map, AllInfo> decoder = JsonToAllInfoConverter();
|
|
|
|
AllInfoJsonCodec({bool isBackwardCompatible = false})
|
|
: encoder = AllInfoToJsonConverter(
|
|
isBackwardCompatible: isBackwardCompatible,
|
|
);
|
|
}
|
|
|
|
class Id {
|
|
final InfoKind kind;
|
|
final String id;
|
|
|
|
Id(this.kind, this.id);
|
|
|
|
String get serializedId => '${kindToString(kind)}/$id';
|
|
}
|
|
|
|
/// Represents the serialization information of [DependencyInfo] with equality
|
|
/// and comparison implemented.
|
|
final class _InfoClass implements Comparable<_InfoClass> {
|
|
final String id;
|
|
final String? mask;
|
|
|
|
_InfoClass._(this.id, this.mask);
|
|
|
|
Map<String, dynamic> toJson() => {'id': id, if (mask != null) 'mask': mask};
|
|
|
|
@override
|
|
int compareTo(_InfoClass other) {
|
|
final value = id.compareTo(other.id);
|
|
if (value != 0) {
|
|
return value;
|
|
}
|
|
|
|
final myMask = mask, otherMask = other.mask;
|
|
|
|
if (myMask == null) {
|
|
return otherMask == null ? 0 : 1;
|
|
}
|
|
if (otherMask == null) {
|
|
return -1;
|
|
}
|
|
|
|
return myMask.compareTo(otherMask);
|
|
}
|
|
}
|