2020-06-11 12:30:49 +00:00
|
|
|
// Copyright (c) 2020, 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.
|
|
|
|
|
|
|
|
/// Classes for representing information about the program structure.
|
2020-06-23 13:14:41 +00:00
|
|
|
library vm_snapshot_analysis.program_info;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-23 13:14:41 +00:00
|
|
|
import 'package:vm_snapshot_analysis/v8_profile.dart';
|
2020-06-22 21:38:16 +00:00
|
|
|
|
2020-06-11 12:30:49 +00:00
|
|
|
/// Represents information about compiled program.
|
2020-06-18 11:57:13 +00:00
|
|
|
class ProgramInfo {
|
|
|
|
static const int rootId = 0;
|
|
|
|
static const int stubsId = 1;
|
|
|
|
static const int unknownId = 2;
|
|
|
|
|
|
|
|
final ProgramInfoNode root;
|
|
|
|
final ProgramInfoNode stubs;
|
|
|
|
final ProgramInfoNode unknown;
|
|
|
|
int _nextId = 3;
|
|
|
|
|
2020-06-22 21:38:16 +00:00
|
|
|
/// V8 snapshot profile if this [ProgramInfo] object was created from an
|
|
|
|
/// output of `--write-v8-snapshot-profile-to=...` flag.
|
2021-04-15 12:03:47 +00:00
|
|
|
SnapshotInfo? snapshotInfo;
|
2020-06-22 21:38:16 +00:00
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
ProgramInfo._(this.root, this.stubs, this.unknown);
|
|
|
|
|
|
|
|
factory ProgramInfo() {
|
|
|
|
final ProgramInfoNode root = ProgramInfoNode._(
|
|
|
|
id: rootId, name: '@shared', type: NodeType.libraryNode, parent: null);
|
|
|
|
|
|
|
|
final ProgramInfoNode stubs = ProgramInfoNode._(
|
|
|
|
id: stubsId, name: '@stubs', type: NodeType.libraryNode, parent: root);
|
|
|
|
root.children[stubs.name] = stubs;
|
|
|
|
|
|
|
|
final ProgramInfoNode unknown = ProgramInfoNode._(
|
|
|
|
id: unknownId,
|
|
|
|
name: '@unknown',
|
|
|
|
type: NodeType.libraryNode,
|
|
|
|
parent: root);
|
|
|
|
root.children[unknown.name] = unknown;
|
|
|
|
|
|
|
|
return ProgramInfo._(root, stubs, unknown);
|
|
|
|
}
|
|
|
|
|
|
|
|
ProgramInfoNode makeNode(
|
2021-04-15 12:03:47 +00:00
|
|
|
{required String name,
|
|
|
|
required ProgramInfoNode parent,
|
|
|
|
required NodeType type}) {
|
|
|
|
return parent.children.putIfAbsent(
|
|
|
|
name,
|
|
|
|
() => ProgramInfoNode._(
|
|
|
|
id: _nextId++, name: name, parent: parent, type: type));
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
|
|
|
|
/// Recursively visit all function nodes, which have [FunctionInfo.info]
|
|
|
|
/// populated.
|
|
|
|
void visit(
|
2021-04-15 12:03:47 +00:00
|
|
|
void Function(String? pkg, String lib, String? cls, String? fun,
|
|
|
|
ProgramInfoNode n)
|
2020-06-18 11:57:13 +00:00
|
|
|
callback) {
|
2021-04-15 12:03:47 +00:00
|
|
|
final context = List<String?>.filled(NodeType.values.length, null);
|
2020-06-18 11:57:13 +00:00
|
|
|
|
|
|
|
void recurse(ProgramInfoNode node) {
|
|
|
|
final prevContext = context[node._type];
|
|
|
|
if (prevContext != null && node._type == NodeType.functionNode.index) {
|
2021-06-24 17:07:48 +00:00
|
|
|
context[node._type] = '$prevContext.${node.name}';
|
2020-06-18 11:57:13 +00:00
|
|
|
} else {
|
|
|
|
context[node._type] = node.name;
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2021-04-15 12:03:47 +00:00
|
|
|
final pkg = context[NodeType.packageNode.index];
|
|
|
|
final lib = context[NodeType.libraryNode.index];
|
|
|
|
final cls = context[NodeType.classNode.index];
|
|
|
|
final mem = context[NodeType.functionNode.index];
|
|
|
|
callback(pkg, lib!, cls, mem, node);
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
for (var child in node.children.values) {
|
|
|
|
recurse(child);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
context[node._type] = prevContext;
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
recurse(root);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-07-03 09:29:10 +00:00
|
|
|
/// Total size of all the nodes in the program.
|
|
|
|
int get totalSize => root.totalSize;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
/// Convert this program info to a JSON map using [infoToJson] to convert
|
|
|
|
/// data attached to nodes into its JSON representation.
|
|
|
|
Map<String, dynamic> toJson() => root.toJson();
|
2020-07-03 09:29:10 +00:00
|
|
|
|
|
|
|
/// Lookup a node in the program given a path to it.
|
2021-04-15 12:03:47 +00:00
|
|
|
ProgramInfoNode? lookup(List<String> path) {
|
2020-07-03 09:29:10 +00:00
|
|
|
var n = root;
|
|
|
|
for (var p in path) {
|
2021-04-15 12:03:47 +00:00
|
|
|
final next = n.children[p];
|
|
|
|
if (next == null) {
|
|
|
|
return null;
|
2020-07-03 09:29:10 +00:00
|
|
|
}
|
2021-04-15 12:03:47 +00:00
|
|
|
n = next;
|
2020-07-03 09:29:10 +00:00
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
enum NodeType {
|
|
|
|
packageNode,
|
|
|
|
libraryNode,
|
|
|
|
classNode,
|
|
|
|
functionNode,
|
|
|
|
other,
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
String _typeToJson(NodeType type) => const {
|
|
|
|
NodeType.packageNode: 'package',
|
|
|
|
NodeType.libraryNode: 'library',
|
|
|
|
NodeType.classNode: 'class',
|
|
|
|
NodeType.functionNode: 'function',
|
2020-07-03 09:29:10 +00:00
|
|
|
NodeType.other: 'other',
|
2021-04-15 12:03:47 +00:00
|
|
|
}[type]!;
|
2020-06-18 11:57:13 +00:00
|
|
|
|
|
|
|
class ProgramInfoNode {
|
|
|
|
final int id;
|
|
|
|
final String name;
|
2021-04-15 12:03:47 +00:00
|
|
|
final ProgramInfoNode? parent;
|
2020-06-18 11:57:13 +00:00
|
|
|
final Map<String, ProgramInfoNode> children = {};
|
|
|
|
final int _type;
|
|
|
|
|
2020-06-24 11:03:55 +00:00
|
|
|
/// Number of bytes in the snapshot which can be attributed to this node.
|
|
|
|
///
|
|
|
|
/// Note that this is neither a shallow size of the object that represents
|
|
|
|
/// this node in the AOT snapshot, nor a cumulative size of its children.
|
|
|
|
/// Instead this size is similar to _retained size_ used by heap profiling
|
|
|
|
/// tools. Snapshot nodes corresponding to the info nodes are viewed as
|
|
|
|
/// a dominator tree and all nodes in the snapshot are partitioned based
|
|
|
|
/// on which node of this tree dominates them.
|
|
|
|
///
|
|
|
|
/// Consider for example the following Dart code:
|
|
|
|
///
|
|
|
|
/// ```dart
|
|
|
|
/// class C {
|
|
|
|
/// void f() { use("something"); }
|
|
|
|
/// void g() { use("something"); }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Assuming that both `C.f` and `C.g` are included into AOT snapshot
|
|
|
|
/// and string `"something"` does not occur anywhere else in the program
|
|
|
|
/// then the size of `"something"` is going to be attributed to `C`.
|
2021-04-15 12:03:47 +00:00
|
|
|
int? size;
|
2020-06-18 11:57:13 +00:00
|
|
|
|
|
|
|
ProgramInfoNode._(
|
2021-04-15 12:03:47 +00:00
|
|
|
{required this.id,
|
|
|
|
required this.name,
|
|
|
|
required this.parent,
|
|
|
|
required NodeType type})
|
2020-06-18 11:57:13 +00:00
|
|
|
: _type = type.index;
|
|
|
|
|
|
|
|
NodeType get type => NodeType.values[_type];
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
|
|
if (size != null) '#size': size,
|
|
|
|
if (_type != NodeType.other.index) '#type': _typeToJson(type),
|
|
|
|
if (children.isNotEmpty)
|
|
|
|
for (var clo in children.entries) clo.key: clo.value.toJson()
|
|
|
|
};
|
2020-07-03 09:29:10 +00:00
|
|
|
|
|
|
|
/// Returns the name of this node prefixed by the [qualifiedName] of its
|
|
|
|
/// [parent].
|
|
|
|
String get qualifiedName {
|
|
|
|
var prefix = '';
|
|
|
|
// Do not include root name or package name (library uri already contains
|
|
|
|
// package name).
|
2021-04-15 12:03:47 +00:00
|
|
|
final p = parent;
|
|
|
|
if (p != null && p.parent != null && p.type != NodeType.packageNode) {
|
|
|
|
prefix = p.qualifiedName;
|
|
|
|
if (p.type != NodeType.libraryNode) {
|
2020-07-03 09:29:10 +00:00
|
|
|
prefix += '.';
|
|
|
|
} else {
|
|
|
|
prefix += '::';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return '$prefix$name';
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
String toString() {
|
2021-06-24 17:07:48 +00:00
|
|
|
return '${_typeToJson(type)} $qualifiedName';
|
2020-07-03 09:29:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns path to this node such that [ProgramInfo.lookup] would return
|
|
|
|
/// this node given its [path].
|
|
|
|
List<String> get path {
|
|
|
|
final result = <String>[];
|
|
|
|
var n = this;
|
|
|
|
while (n.parent != null) {
|
|
|
|
result.add(n.name);
|
2021-04-15 12:03:47 +00:00
|
|
|
n = n.parent!;
|
2020-07-03 09:29:10 +00:00
|
|
|
}
|
|
|
|
return result.reversed.toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cumulative size of this node and all of its children.
|
|
|
|
int get totalSize {
|
|
|
|
return (size ?? 0) +
|
|
|
|
children.values.fold<int>(0, (s, n) => s + n.totalSize);
|
|
|
|
}
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
/// Computes the size difference between two [ProgramInfo].
|
|
|
|
ProgramInfo computeDiff(ProgramInfo oldInfo, ProgramInfo newInfo) {
|
|
|
|
final programDiff = ProgramInfo();
|
|
|
|
|
|
|
|
var path = <Object>[];
|
2021-04-15 12:03:47 +00:00
|
|
|
void recurse(ProgramInfoNode? oldNode, ProgramInfoNode? newNode) {
|
2020-06-18 11:57:13 +00:00
|
|
|
if (oldNode?.size != newNode?.size) {
|
|
|
|
var diffNode = programDiff.root;
|
|
|
|
for (var i = 0; i < path.length; i += 2) {
|
2021-04-15 12:03:47 +00:00
|
|
|
final name = path[i] as String;
|
|
|
|
final type = path[i + 1] as NodeType;
|
2020-06-18 11:57:13 +00:00
|
|
|
diffNode =
|
|
|
|
programDiff.makeNode(name: name, parent: diffNode, type: type);
|
|
|
|
}
|
2021-04-15 12:03:47 +00:00
|
|
|
diffNode.size =
|
|
|
|
(diffNode.size ?? 0) + (newNode?.size ?? 0) - (oldNode?.size ?? 0);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
for (var key in _allKeys(newNode?.children, oldNode?.children)) {
|
|
|
|
final newChildNode = newNode != null ? newNode.children[key] : null;
|
|
|
|
final oldChildNode = oldNode != null ? oldNode.children[key] : null;
|
|
|
|
path.add(key);
|
2021-04-15 12:03:47 +00:00
|
|
|
path.add((oldChildNode?.type ?? newChildNode?.type)!);
|
2020-06-18 11:57:13 +00:00
|
|
|
recurse(oldChildNode, newChildNode);
|
|
|
|
path.removeLast();
|
|
|
|
path.removeLast();
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
recurse(oldInfo.root, newInfo.root);
|
2020-06-11 12:30:49 +00:00
|
|
|
|
|
|
|
return programDiff;
|
|
|
|
}
|
|
|
|
|
2021-04-15 12:03:47 +00:00
|
|
|
Iterable<T> _allKeys<T>(Map<T, dynamic>? a, Map<T, dynamic>? b) {
|
2020-06-11 12:30:49 +00:00
|
|
|
return <T>{...?a?.keys, ...?b?.keys};
|
|
|
|
}
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
class Histogram {
|
2020-06-11 12:30:49 +00:00
|
|
|
/// Rule used to produce this histogram. Specifies how bucket names
|
|
|
|
/// are constructed given (library-uri,class-name,function-name) tuples and
|
|
|
|
/// how these bucket names can be deconstructed back into human readable form.
|
2020-06-22 21:44:36 +00:00
|
|
|
final BucketInfo bucketInfo;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
|
|
|
/// Histogram buckets.
|
|
|
|
final Map<String, int> buckets;
|
|
|
|
|
|
|
|
/// Bucket names sorted by the size of the corresponding bucket in descending
|
|
|
|
/// order.
|
|
|
|
final List<String> bySize;
|
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
final int totalSize;
|
|
|
|
|
|
|
|
int get length => bySize.length;
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
Histogram._(this.bucketInfo, this.buckets)
|
|
|
|
: bySize = buckets.keys.toList(growable: false)
|
2021-04-15 12:03:47 +00:00
|
|
|
..sort((a, b) => buckets[b]! - buckets[a]!),
|
2020-06-22 21:44:36 +00:00
|
|
|
totalSize = buckets.values.fold(0, (sum, size) => sum + size);
|
|
|
|
|
|
|
|
static Histogram fromIterable<T>(
|
|
|
|
Iterable<T> entries, {
|
2021-04-15 12:03:47 +00:00
|
|
|
required int Function(T) sizeOf,
|
|
|
|
required String Function(T) bucketFor,
|
|
|
|
required BucketInfo bucketInfo,
|
2020-06-22 21:44:36 +00:00
|
|
|
}) {
|
|
|
|
final buckets = <String, int>{};
|
|
|
|
|
|
|
|
for (var e in entries) {
|
|
|
|
final bucket = bucketFor(e);
|
|
|
|
final size = sizeOf(e);
|
|
|
|
buckets[bucket] = (buckets[bucket] ?? 0) + size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Histogram._(bucketInfo, buckets);
|
|
|
|
}
|
2020-07-03 09:29:10 +00:00
|
|
|
|
|
|
|
/// Rebuckets the histogram given the new bucketing rule.
|
|
|
|
Histogram map(String Function(String) bucketFor) {
|
|
|
|
return Histogram.fromIterable(buckets.keys,
|
2021-04-15 12:03:47 +00:00
|
|
|
sizeOf: (key) => buckets[key]!,
|
2020-07-03 09:29:10 +00:00
|
|
|
bucketFor: bucketFor,
|
|
|
|
bucketInfo: bucketInfo);
|
|
|
|
}
|
2020-06-22 21:44:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Construct the histogram of specific [type] given a [ProgramInfo].
|
2021-04-15 12:03:47 +00:00
|
|
|
///
|
|
|
|
/// [filter] glob can be provided to skip some of the nodes in the [info]:
|
|
|
|
/// a string is created which contains library name, class name and function
|
|
|
|
/// name for the given node and if this string does not match the [filter]
|
|
|
|
/// glob then this node is ignored.
|
2020-06-22 21:44:36 +00:00
|
|
|
Histogram computeHistogram(ProgramInfo info, HistogramType type,
|
2021-04-15 12:03:47 +00:00
|
|
|
{String? filter}) {
|
|
|
|
bool Function(String, String?, String?) matchesFilter;
|
2020-06-22 21:44:36 +00:00
|
|
|
|
|
|
|
if (filter != null) {
|
|
|
|
final re = RegExp(filter.replaceAll('*', '.*'), caseSensitive: false);
|
2021-04-15 12:03:47 +00:00
|
|
|
matchesFilter =
|
2021-06-24 17:07:48 +00:00
|
|
|
(lib, cls, fun) => re.hasMatch("$lib::${cls ?? ''}.${fun ?? ''}");
|
2020-06-22 21:44:36 +00:00
|
|
|
} else {
|
|
|
|
matchesFilter = (_, __, ___) => true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type == HistogramType.byNodeType) {
|
|
|
|
final Set<int> filteredNodes = {};
|
|
|
|
if (filter != null) {
|
|
|
|
info.visit((pkg, lib, cls, fun, node) {
|
|
|
|
if (matchesFilter(lib, cls, fun)) {
|
|
|
|
filteredNodes.add(node.id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2021-04-15 12:03:47 +00:00
|
|
|
final snapshotInfo = info.snapshotInfo!;
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
return Histogram.fromIterable<Node>(
|
2021-04-15 12:03:47 +00:00
|
|
|
snapshotInfo.snapshot.nodes.where((n) =>
|
2020-06-22 21:44:36 +00:00
|
|
|
filter == null ||
|
2021-04-15 12:03:47 +00:00
|
|
|
filteredNodes.contains(snapshotInfo.ownerOf(n).id)),
|
2020-06-22 21:44:36 +00:00
|
|
|
sizeOf: (n) {
|
|
|
|
return n.selfSize;
|
|
|
|
},
|
|
|
|
bucketFor: (n) => n.type,
|
|
|
|
bucketInfo: const BucketInfo(nameComponents: ['Type']));
|
|
|
|
} else {
|
2020-06-11 12:30:49 +00:00
|
|
|
final buckets = <String, int>{};
|
2021-04-15 12:03:47 +00:00
|
|
|
final bucketing = Bucketing._forType[type]!;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
info.visit((pkg, lib, cls, fun, node) {
|
2021-04-15 12:03:47 +00:00
|
|
|
final sz = node.size;
|
|
|
|
if (sz == null || sz == 0) {
|
2020-06-22 21:44:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!matchesFilter(lib, cls, fun)) {
|
|
|
|
return;
|
|
|
|
}
|
2020-07-03 09:29:10 +00:00
|
|
|
final bucket = bucketing.bucketFor(pkg, lib, cls, fun);
|
2021-04-15 12:03:47 +00:00
|
|
|
buckets[bucket] = (buckets[bucket] ?? 0) + sz;
|
2020-06-11 12:30:49 +00:00
|
|
|
});
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
return Histogram._(bucketing, buckets);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum HistogramType {
|
|
|
|
bySymbol,
|
|
|
|
byClass,
|
|
|
|
byLibrary,
|
|
|
|
byPackage,
|
2020-06-22 21:44:36 +00:00
|
|
|
byNodeType,
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
class BucketInfo {
|
2020-06-11 12:30:49 +00:00
|
|
|
/// Specifies which human readable name components can be extracted from
|
|
|
|
/// the bucket name.
|
2020-06-22 21:44:36 +00:00
|
|
|
final List<String> nameComponents;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
/// Deconstructs bucket name into human readable components (the order matches
|
|
|
|
/// one returned by [nameComponents]).
|
|
|
|
List<String> namesFromBucket(String bucket) => [bucket];
|
|
|
|
|
2021-04-15 12:03:47 +00:00
|
|
|
const BucketInfo({required this.nameComponents});
|
2020-06-22 21:44:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract class Bucketing extends BucketInfo {
|
2020-06-11 12:30:49 +00:00
|
|
|
/// Constructs the bucket name from the given library name [lib], class name
|
|
|
|
/// [cls] and function name [fun].
|
2021-04-15 12:03:47 +00:00
|
|
|
String bucketFor(String? pkg, String lib, String? cls, String? fun);
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2021-04-15 12:03:47 +00:00
|
|
|
const Bucketing({required List<String> nameComponents})
|
2020-06-22 21:44:36 +00:00
|
|
|
: super(nameComponents: nameComponents);
|
2020-06-11 12:30:49 +00:00
|
|
|
|
|
|
|
static const _forType = {
|
|
|
|
HistogramType.bySymbol: _BucketBySymbol(),
|
|
|
|
HistogramType.byClass: _BucketByClass(),
|
|
|
|
HistogramType.byLibrary: _BucketByLibrary(),
|
|
|
|
HistogramType.byPackage: _BucketByPackage(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A combination of characters that is unlikely to occur in the symbol name.
|
|
|
|
const String _nameSeparator = ';;;';
|
|
|
|
|
|
|
|
class _BucketBySymbol extends Bucketing {
|
|
|
|
@override
|
2021-04-15 12:03:47 +00:00
|
|
|
String bucketFor(String? pkg, String lib, String? cls, String? fun) {
|
2020-06-18 11:57:13 +00:00
|
|
|
if (fun == null) {
|
2021-06-24 17:07:48 +00:00
|
|
|
return '@other$_nameSeparator';
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2021-06-24 17:07:48 +00:00
|
|
|
return '$lib$_nameSeparator${cls ?? ''}${cls != '' && cls != null ? '.' : ''}$fun';
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
List<String> namesFromBucket(String bucket) => bucket.split(_nameSeparator);
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
const _BucketBySymbol() : super(nameComponents: const ['Library', 'Symbol']);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class _BucketByClass extends Bucketing {
|
|
|
|
@override
|
2021-04-15 12:03:47 +00:00
|
|
|
String bucketFor(String? pkg, String lib, String? cls, String? fun) {
|
2020-06-18 11:57:13 +00:00
|
|
|
if (cls == null) {
|
2021-06-24 17:07:48 +00:00
|
|
|
return '@other$_nameSeparator';
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2021-06-24 17:07:48 +00:00
|
|
|
return '$lib$_nameSeparator$cls';
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
List<String> namesFromBucket(String bucket) => bucket.split(_nameSeparator);
|
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
const _BucketByClass() : super(nameComponents: const ['Library', 'Class']);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class _BucketByLibrary extends Bucketing {
|
|
|
|
@override
|
2021-06-24 17:07:48 +00:00
|
|
|
String bucketFor(String? pkg, String lib, String? cls, String? fun) => lib;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
const _BucketByLibrary() : super(nameComponents: const ['Library']);
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class _BucketByPackage extends Bucketing {
|
|
|
|
@override
|
2021-04-15 12:03:47 +00:00
|
|
|
String bucketFor(String? pkg, String lib, String? cls, String? fun) =>
|
2020-06-18 11:57:13 +00:00
|
|
|
pkg ?? lib;
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-22 21:44:36 +00:00
|
|
|
const _BucketByPackage() : super(nameComponents: const ['Package']);
|
2020-06-18 11:57:13 +00:00
|
|
|
}
|
2020-06-11 12:30:49 +00:00
|
|
|
|
2020-06-18 11:57:13 +00:00
|
|
|
String packageOf(String lib) {
|
|
|
|
if (lib.startsWith('package:')) {
|
|
|
|
final separatorPos = lib.indexOf('/');
|
|
|
|
return lib.substring(0, separatorPos);
|
|
|
|
} else {
|
|
|
|
return lib;
|
2020-06-11 12:30:49 +00:00
|
|
|
}
|
|
|
|
}
|