Files
sdk/pkg/dev_compiler/tool/input_sdk/private/debugger.dart
T
Alan Knight f3bd910e10 Only use MapFormatter for SDK maps (HashMap and LinkedHashMap)
For classes that implement Map, using MapFormatter can result in many,
many custom format operations. Also, the presentation is not that
useful. We get e.g. a list of indices that then have a name and value
beside them, so it just duplicates the Object view, but with extra
non-useful data.

Change-Id: I74ed34521778bddae9cdad975339925d7c181bf3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100282
Commit-Queue: Alan Knight <alanknight@google.com>
Reviewed-by: Jacob Richman <jacobr@google.com>
2019-04-24 22:08:45 +00:00

1017 lines
32 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.
library dart._debugger;
import 'dart:_foreign_helper' show JS;
import 'dart:_interceptors' show JSArray;
import 'dart:_js_helper' show InternalMap;
import 'dart:_runtime' as dart;
import 'dart:core';
import 'dart:collection';
import 'dart:html' as html;
import 'dart:math';
part 'profile.dart';
/// JsonMLConfig object to pass to devtools to specify how an Object should
/// be displayed. skipDart signals that an object should not be formatted
/// by the Dart formatter. This is used to specify that an Object
/// should just be displayed using the regular JavaScript view instead of a
/// custom Dart view. For example, this is used to display the JavaScript view
/// of a Dart Function as a child of the regular Function object. keyToString
/// signals that a map key object should have its toString() displayed by
/// the Dart formatter.
///
/// We'd like this to be an enum, but we can't because it's a dev_compiler bug.
class JsonMLConfig {
const JsonMLConfig(this.name);
final String name;
static const none = JsonMLConfig("none");
static const skipDart = JsonMLConfig("skipDart");
static const keyToString = JsonMLConfig("keyToString");
static const asClass = JsonMLConfig("asClass");
static const asObject = JsonMLConfig("asObject");
static const asMap = JsonMLConfig("asMap");
toString() => "JsonMLConfig($name)";
}
int _maxSpanLength = 100;
var _devtoolsFormatter = JsonMLFormatter(DartFormatter());
/// We truncate a toString() longer than [maxStringLength].
int maxFormatterStringLength = 100;
String _typeof(object) => JS('String', 'typeof #', object);
List<String> getOwnPropertyNames(object) =>
JSArray<String>.of(dart.getOwnPropertyNames(object));
List getOwnPropertySymbols(object) =>
JS('List', 'Object.getOwnPropertySymbols(#)', object);
// TODO(jacobr): move this to dart:js and fully implement.
class JSNative {
// Name may be a String or a Symbol.
static getProperty(object, name) => JS('', '#[#]', object, name);
// Name may be a String or a Symbol.
static setProperty(object, name, value) =>
JS('', '#[#]=#', object, name, value);
}
void addMetadataChildren(object, Set<NameValuePair> ret) {
ret.add(NameValuePair(
name: "[[class]]",
value: dart.getReifiedType(object),
config: JsonMLConfig.asClass));
}
/// Add properties from a signature definition [sig] for [object].
/// Walk the prototype chain if [walkProtypeChain] is set.
/// Tag types on function typed properties of [object] if [tagTypes] is set.
///
void addPropertiesFromSignature(
sig, Set<NameValuePair> properties, object, bool walkPrototypeChain,
{tagTypes = false}) {
// Including these property names doesn't add any value and just clutters
// the debugger output.
// TODO(jacobr): consider adding runtimeType to this list.
var skippedNames = Set()..add('hashCode');
var objectPrototype = JS('', 'Object.prototype');
while (sig != null && !identical(sig, objectPrototype)) {
for (var symbol in getOwnPropertySymbols(sig)) {
var dartName = symbolName(symbol);
String dartXPrefix = 'dartx.';
if (dartName.startsWith(dartXPrefix)) {
dartName = dartName.substring(dartXPrefix.length);
}
if (skippedNames.contains(dartName)) continue;
var value = safeGetProperty(object, symbol);
// Tag the function with its runtime type.
if (tagTypes && _typeof(value) == 'function') {
dart.fn(value, JS('', '#[#]', sig, symbol));
}
properties.add(NameValuePair(name: dartName, value: value));
}
for (var name in getOwnPropertyNames(sig)) {
var value = safeGetProperty(object, name);
if (skippedNames.contains(name)) continue;
// Tag the function with its runtime type.
if (tagTypes && _typeof(value) == 'function') {
dart.fn(value, JS('', '#[#]', sig, name));
}
properties.add(NameValuePair(name: name, value: value));
}
if (!walkPrototypeChain) break;
sig = dart.getPrototypeOf(sig);
}
}
/// Sort properties sorting public names before private names.
List<NameValuePair> sortProperties(Iterable<NameValuePair> properties) {
var sortedProperties = properties.toList();
sortedProperties.sort((a, b) {
var aPrivate = a.name.startsWith('_');
var bPrivate = b.name.startsWith('_');
if (aPrivate != bPrivate) return aPrivate ? 1 : -1;
return a.name.compareTo(b.name);
});
return sortedProperties;
}
String getObjectTypeName(object) {
var reifiedType = dart.getReifiedType(object);
if (reifiedType == null) {
if (_typeof(object) == 'function') {
return '[[Raw JavaScript Function]]';
}
return '<Error getting type name>';
}
return getTypeName(reifiedType);
}
String getTypeName(type) {
// TODO(jacobr): it would be nice if there was a way we could distinguish
// between a List<dynamic> created from Dart and an Array passed in from
// JavaScript.
return dart.typeName(type);
}
String safePreview(object, config) {
try {
var preview = _devtoolsFormatter._simpleFormatter.preview(object, config);
if (preview != null) return preview;
return object.toString();
} catch (e) {
return '<Exception thrown> $e';
}
}
String symbolName(symbol) {
var name = symbol.toString();
assert(name.startsWith('Symbol('));
return name.substring('Symbol('.length, name.length - 1);
}
bool hasMethod(object, String name) {
try {
return dart.hasMethod(object, name);
} catch (e) {
return false;
}
}
/// [JsonMLFormatter] consumes [NameValuePair] objects and
class NameValuePair {
NameValuePair(
{this.name,
this.value,
this.config = JsonMLConfig.none,
this.hideName = false});
// Define equality and hashCode so that NameValuePair can be used
// in a Set to dedupe entries with duplicate names.
bool operator ==(other) {
if (other is! NameValuePair) return false;
if (this.hideName || other.hideName) return identical(this, other);
return other.name == name;
}
int get hashCode => name.hashCode;
final String name;
final Object value;
final JsonMLConfig config;
final bool hideName;
String get displayName => hideName ? '' : name;
}
class MapEntry {
MapEntry({this.key, this.value});
final Object key;
final Object value;
}
class IterableSpan {
IterableSpan(this.start, this.end, this.iterable);
final int start;
final int end;
final Iterable iterable;
int get length => end - start;
/// Using length - .5, a list of length 10000 results in a
/// maxPowerOfSubsetSize of 1, so the list will be broken up into 100,
/// 100-length subsets. A list of length 10001 results in a
/// maxPowerOfSubsetSize of 2, so the list will be broken up into 1
/// 10000-length subset and 1 1-length subset.
int get maxPowerOfSubsetSize =>
(log(length - .5) / log(_maxSpanLength)).truncate();
int get subsetSize => pow(_maxSpanLength, maxPowerOfSubsetSize);
Map<int, dynamic> asMap() =>
iterable.skip(start).take(length).toList().asMap();
List<NameValuePair> children() {
var children = <NameValuePair>[];
if (length <= _maxSpanLength) {
asMap().forEach((i, element) {
children
.add(NameValuePair(name: (i + start).toString(), value: element));
});
} else {
for (var i = start; i < end; i += subsetSize) {
var subSpan = IterableSpan(i, min(end, subsetSize + i), iterable);
if (subSpan.length == 1) {
children.add(
NameValuePair(name: i.toString(), value: iterable.elementAt(i)));
} else {
children.add(NameValuePair(
name: '[${i}...${subSpan.end - 1}]',
value: subSpan,
hideName: true));
}
}
}
return children;
}
}
class Library {
Library(this.name, this.object);
final String name;
final Object object;
}
class NamedConstructor {
NamedConstructor(this.object);
final Object object;
}
class HeritageClause {
HeritageClause(this.name, this.types);
final String name;
final List types;
}
Object safeGetProperty(Object protoChain, Object name) {
try {
return JSNative.getProperty(protoChain, name);
} catch (e) {
return '<Exception thrown> $e';
}
}
safeProperties(object) => Map.fromIterable(
getOwnPropertyNames(object)
.where((each) => safeGetProperty(object, each) != null),
key: (name) => name,
value: (name) => safeGetProperty(object, name));
/// Class to simplify building the JsonML objects expected by the
/// Devtools Formatter API.
class JsonMLElement {
dynamic _attributes;
List _jsonML;
JsonMLElement(tagName) {
_attributes = JS('', '{}');
_jsonML = [tagName, _attributes];
}
appendChild(element) {
_jsonML.add(element.toJsonML());
}
JsonMLElement createChild(String tagName) {
var c = JsonMLElement(tagName);
_jsonML.add(c.toJsonML());
return c;
}
JsonMLElement createObjectTag(object) =>
createChild('object')..addAttribute('object', object);
void setStyle(String style) {
_attributes.style = style;
}
addStyle(String style) {
if (_attributes.style == null) {
_attributes.style = style;
} else {
_attributes.style += style;
}
}
addAttribute(key, value) {
JSNative.setProperty(_attributes, key, value);
}
createTextChild(String text) {
_jsonML.add(text);
}
toJsonML() => _jsonML;
}
/// Whether an object is a native JavaScript type where we should display the
/// JavaScript view of the object instead of the custom Dart specific render
/// of properties.
bool isNativeJavaScriptObject(object) {
var type = _typeof(object);
if (type != 'object' && type != 'function') return true;
// Consider all regular JS objects that do not represent Dart modules native
// JavaScript objects.
if (dart.isJsInterop(object) && dart.getModuleName(object) == null) {
return true;
}
// Treat Node objects as a native JavaScript type as the regular DOM render
// in devtools is superior to the dart specific view.
return object is html.Node;
}
/// Class implementing the Devtools Formatter API described by:
/// https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U
/// Specifically, a formatter implements a header, hasBody, and body method.
/// This class renders the simple structured format objects [_simpleFormatter]
/// provides as JsonML.
class JsonMLFormatter {
// TODO(jacobr): define a SimpleFormatter base class that DartFormatter
// implements if we decide to use this class elsewhere. We specify that the
// type is DartFormatter here purely to get type checking benefits not because
// this class is really intended to only support instances of type
// DartFormatter.
DartFormatter _simpleFormatter;
bool customFormattersOn = false;
JsonMLFormatter(this._simpleFormatter);
void setMaxSpanLengthForTestingOnly(int spanLength) {
_maxSpanLength = spanLength;
}
header(object, config) {
customFormattersOn = true;
if (config == JsonMLConfig.skipDart || isNativeJavaScriptObject(object)) {
return null;
}
var c = _simpleFormatter.preview(object, config);
if (c == null) return null;
if (config == JsonMLConfig.keyToString) {
c = object.toString();
}
// Indicate this is a Dart Object by using a Dart background color.
// This is stylistically a bit ugly but it eases distinguishing Dart and
// JS objects.
var element = JsonMLElement('span')
..setStyle('background-color: #d9edf7;color: black')
..createTextChild(c);
return element.toJsonML();
}
bool hasBody(object, config) => _simpleFormatter.hasChildren(object, config);
body(object, config) {
var body = JsonMLElement('ol')
..setStyle('list-style-type: none;'
'padding-left: 0px;'
'margin-top: 0px;'
'margin-bottom: 0px;'
'margin-left: 12px;');
if (object is StackTrace) {
body.addStyle('background-color: thistle;color: rgb(196, 26, 22);');
}
var children = _simpleFormatter.children(object, config);
if (children == null) return body.toJsonML();
for (NameValuePair child in children) {
var li = body.createChild('li');
li.setStyle("padding-left: 13px;");
// The value is indented when it is on a different line from the name
// by setting right padding of the name to -13px and the padding of the
// value to 13px.
JsonMLElement nameSpan;
var valueStyle = '';
if (!child.hideName) {
nameSpan = JsonMLElement('span')
..createTextChild(
child.displayName.isNotEmpty ? '${child.displayName}: ' : '')
..setStyle(
'background-color: thistle; color: rgb(136, 19, 145); margin-right: -13px');
valueStyle = 'margin-left: 13px';
}
if (_typeof(child.value) == 'object' ||
_typeof(child.value) == 'function') {
var valueSpan = JsonMLElement('span')..setStyle(valueStyle);
valueSpan.createObjectTag(child.value)
..addAttribute('config', child.config);
if (nameSpan != null) {
li.appendChild(nameSpan);
}
li.appendChild(valueSpan);
} else {
var line = li.createChild('span');
if (nameSpan != null) {
line.appendChild(nameSpan);
}
line.appendChild(JsonMLElement('span')
..createTextChild(safePreview(child.value, child.config))
..setStyle(valueStyle));
}
}
return body.toJsonML();
}
}
abstract class Formatter {
bool accept(object, config);
String preview(object);
bool hasChildren(object);
List<NameValuePair> children(object);
}
class DartFormatter {
List<Formatter> _formatters;
DartFormatter() {
// The order of formatters matters as formatters earlier in the list take
// precedence.
_formatters = [
ObjectInternalsFormatter(),
ClassFormatter(),
TypeFormatter(),
NamedConstructorFormatter(),
MapFormatter(),
MapOverviewFormatter(),
IterableFormatter(),
IterableSpanFormatter(),
MapEntryFormatter(),
StackTraceFormatter(),
ErrorAndExceptionFormatter(),
FunctionFormatter(),
HeritageClauseFormatter(),
LibraryModuleFormatter(),
LibraryFormatter(),
ObjectFormatter(),
];
}
String preview(object, config) {
try {
if (object == null ||
object is num ||
object is String ||
isNativeJavaScriptObject(object)) {
return object.toString();
}
for (var formatter in _formatters) {
if (formatter.accept(object, config)) return formatter.preview(object);
}
} catch (e, trace) {
// Log formatter internal errors as unfortunately the devtools cannot
// be used to debug formatter errors.
html.window.console.error("Caught exception $e\n trace:\n$trace");
}
return null;
}
bool hasChildren(object, config) {
if (object == null) return false;
try {
for (var formatter in _formatters) {
if (formatter.accept(object, config))
return formatter.hasChildren(object);
}
} catch (e, trace) {
// See comment for preview.
html.window.console
.error("[hasChildren] Caught exception $e\n trace:\n$trace");
}
return false;
}
List<NameValuePair> children(object, config) {
try {
if (object != null) {
for (var formatter in _formatters) {
if (formatter.accept(object, config))
return formatter.children(object);
}
}
} catch (e, trace) {
// See comment for preview.
html.window.console.error("Caught exception $e\n trace:\n$trace");
}
return <NameValuePair>[];
}
}
/// Default formatter for Dart Objects.
class ObjectFormatter extends Formatter {
bool accept(object, config) => !isNativeJavaScriptObject(object);
String preview(object) {
var typeName = getObjectTypeName(object);
try {
// An explicit toString() call might not actually be a string. This way
// we're sure.
var toString = "$object";
if (toString.length > maxFormatterStringLength) {
toString = toString.substring(0, maxFormatterStringLength - 3) + "...";
}
// The default toString() will be "Instance of 'Foo'", in which case we
// don't need any further indication of the class.
if (toString.contains(typeName)) {
return toString;
} else {
// If there's no class indication, e.g. an Int64 that just prints as a
// number, then add the class name.
return "$toString ($typeName)";
}
} catch (e) {}
// We will only get here if there was an error getting the toString, in
// which case we just use the type name.
return typeName;
}
bool hasChildren(object) => true;
List<NameValuePair> children(object) {
var type = dart.getType(object);
var ret = LinkedHashSet<NameValuePair>();
// We use a Set rather than a List to avoid duplicates.
var fields = Set<NameValuePair>();
addPropertiesFromSignature(dart.getFields(type), fields, object, true);
var getters = Set<NameValuePair>();
addPropertiesFromSignature(dart.getGetters(type), getters, object, true);
ret.addAll(sortProperties(fields));
ret.addAll(sortProperties(getters));
addMetadataChildren(object, ret);
return ret.toList();
}
}
/// Show the object instance members and a reduced preview.
///
/// Used as a sub-entry to show the internals of objects that have a different
/// primary format. For example, a Map shows the key-value pairs, but this makes
/// the internals of the map visible for debugging.
class ObjectInternalsFormatter extends ObjectFormatter {
bool accept(object, config) =>
super.accept(object, config) && config == JsonMLConfig.asObject;
// A minimal preview because we expect a full preview is already shown in a
// parent formatter.
String preview(object) {
return getObjectTypeName(object);
}
}
/// Formatter for module Dart Library objects.
class LibraryModuleFormatter implements Formatter {
accept(object, config) => dart.getModuleName(object) != null;
bool hasChildren(object) => true;
String preview(object) {
var libraryNames = dart.getModuleName(object).split('/');
// Library names are received with a repeat directory name, so strip the
// last directory entry here to make the path cleaner. For example, the
// library "third_party/dart/utf/utf" shoud display as
// "third_party/dart/utf/".
if (libraryNames.length > 1 &&
libraryNames.last == libraryNames[libraryNames.length - 2]) {
libraryNames[libraryNames.length - 1] = '';
}
return 'Library Module: ${libraryNames.join('/')}';
}
List<NameValuePair> children(object) {
var children = LinkedHashSet<NameValuePair>();
for (var name in getOwnPropertyNames(object)) {
var value = safeGetProperty(object, name);
children.add(NameValuePair(
name: name, value: Library(name, value), hideName: true));
}
return children.toList();
}
}
class LibraryFormatter implements Formatter {
var genericParameters = HashMap<String, String>();
accept(object, config) => object is Library;
bool hasChildren(object) => true;
String preview(object) => object.name;
List<NameValuePair> children(object) {
// Maintain library member order rather than sorting members as is the
// case for class members.
var children = LinkedHashSet<NameValuePair>();
var objectProperties = safeProperties(object.object);
objectProperties.forEach((name, value) {
// Skip the generic constructors for each class as users are only
// interested in seeing the actual classes.
if (dart.getGenericTypeCtor(value) != null) return;
children.add(dart.isType(value)
? classChild(name, value)
: NameValuePair(name: name, value: value));
});
return children.toList();
}
classChild(String name, Object child) {
var typeName = getTypeName(child);
return NameValuePair(
name: typeName, value: child, config: JsonMLConfig.asClass);
}
}
/// Formatter for Dart Function objects.
/// Dart functions happen to be regular JavaScript Function objects but
/// we can distinguish them based on whether they have been tagged with
/// runtime type information.
class FunctionFormatter implements Formatter {
accept(object, config) {
if (_typeof(object) != 'function') return false;
return dart.getReifiedType(object) != null;
}
bool hasChildren(object) => true;
String preview(object) {
// The debugger can createa a preview of a FunctionType while it's being
// constructed (before argument types exist), so we need to catch errors.
try {
return dart.typeName(dart.getReifiedType(object));
} catch (e) {
return safePreview(object, JsonMLConfig.none);
}
}
List<NameValuePair> children(object) => <NameValuePair>[
NameValuePair(name: 'signature', value: preview(object)),
NameValuePair(
name: 'JavaScript Function',
value: object,
config: JsonMLConfig.skipDart)
];
}
/// Formatter for Objects that implement Map but are not system Maps.
///
/// This shows two sub-views, one for instance fields and one for
/// Map key/value pairs.
class MapOverviewFormatter implements Formatter {
// Because this comes after MapFormatter in the list, internal
// maps will be picked up by that formatter.
accept(object, config) => object is Map;
bool hasChildren(object) => true;
String preview(object) {
Map map = object;
try {
return '${getObjectTypeName(map)}';
} catch (e) {
return safePreview(object, JsonMLConfig.none);
}
}
List<NameValuePair> children(object) => [
NameValuePair(
name: "[[instance view]]",
value: object,
config: JsonMLConfig.asObject),
NameValuePair(
name: "[[entries]]", value: object, config: JsonMLConfig.asMap)
];
}
/// Formatter for Dart Map objects.
///
/// This is only used for internal maps, or when shown as [[entries]]
/// from MapOverViewFormatter.
class MapFormatter implements Formatter {
accept(object, config) =>
object is InternalMap || config == JsonMLConfig.asMap;
bool hasChildren(object) => true;
String preview(object) {
Map map = object;
try {
return '${getObjectTypeName(map)} length ${map.length}';
} catch (e) {
return safePreview(object, JsonMLConfig.none);
}
}
List<NameValuePair> children(object) {
// TODO(jacobr): be lazier about enumerating contents of Maps that are not
// the build in LinkedHashMap class.
// TODO(jacobr): handle large Maps better.
Map map = object;
var entries = LinkedHashSet<NameValuePair>();
map.forEach((key, value) {
var entryWrapper = MapEntry(key: key, value: value);
entries.add(
NameValuePair(name: entries.length.toString(), value: entryWrapper));
});
addMetadataChildren(object, entries);
return entries.toList();
}
}
/// Formatter for Dart Iterable objects including List and Set.
class IterableFormatter implements Formatter {
bool accept(object, config) => object is Iterable;
String preview(object) {
Iterable iterable = object;
try {
var length = iterable.length;
return '${getObjectTypeName(iterable)} length $length';
} catch (_) {
return '${getObjectTypeName(iterable)}';
}
}
bool hasChildren(object) => true;
List<NameValuePair> children(object) {
// TODO(jacobr): be lazier about enumerating contents of Iterables that
// are not the built in Set or List types.
// TODO(jacobr): handle large Iterables better.
// TODO(jacobr): consider only using numeric indices
var children = LinkedHashSet<NameValuePair>();
children.addAll(IterableSpan(0, object.length, object).children());
// TODO(jacobr): provide a link to show regular class properties here.
// required for subclasses of iterable, etc.
addMetadataChildren(object, children);
return children.toList();
}
}
class NamedConstructorFormatter implements Formatter {
accept(object, config) => object is NamedConstructor;
// TODO(bmilligan): Display the signature of the named constructor as the
// preview.
String preview(object) => 'Named Constructor';
bool hasChildren(object) => true;
List<NameValuePair> children(object) => <NameValuePair>[
NameValuePair(
name: 'JavaScript Function',
value: object,
config: JsonMLConfig.skipDart)
];
}
/// Formatter for synthetic MapEntry objects used to display contents of a Map
/// cleanly.
class MapEntryFormatter implements Formatter {
accept(object, config) => object is MapEntry;
String preview(object) {
MapEntry entry = object;
return '${safePreview(entry.key, JsonMLConfig.none)} => ${safePreview(entry.value, JsonMLConfig.none)}';
}
bool hasChildren(object) => true;
List<NameValuePair> children(object) => <NameValuePair>[
NameValuePair(
name: 'key', value: object.key, config: JsonMLConfig.keyToString),
NameValuePair(name: 'value', value: object.value)
];
}
/// Formatter for Dart Iterable objects including List and Set.
class HeritageClauseFormatter implements Formatter {
bool accept(object, config) => object is HeritageClause;
String preview(object) {
HeritageClause clause = object;
var typeNames = clause.types.map(getTypeName);
return '${clause.name} ${typeNames.join(", ")}';
}
bool hasChildren(object) => true;
List<NameValuePair> children(object) {
HeritageClause clause = object;
var children = <NameValuePair>[];
for (var type in clause.types) {
children.add(NameValuePair(value: type, config: JsonMLConfig.asClass));
}
return children;
}
}
/// Formatter for synthetic IterableSpan objects used to display contents of
/// an Iterable cleanly.
class IterableSpanFormatter implements Formatter {
accept(object, config) => object is IterableSpan;
String preview(object) {
return '[${object.start}...${object.end - 1}]';
}
bool hasChildren(object) => true;
List<NameValuePair> children(object) => object.children();
}
/// Formatter for Dart Errors and Exceptions.
class ErrorAndExceptionFormatter extends ObjectFormatter {
static final RegExp _pattern = RegExp(r'\d+\:\d+');
accept(object, config) => object is Error || object is Exception;
bool hasChildren(object) => true;
String preview(object) {
var trace = dart.stackTrace(object);
// TODO(vsm): Pull our stack mapping logic here. We should aim to
// provide the first meaningful stack frame.
var line = '$trace'.split('\n').firstWhere(
(l) =>
l.contains(_pattern) &&
!l.contains('dart:sdk') &&
!l.contains('dart_sdk'),
orElse: () => null);
return line != null ? '${object} at ${line}' : '${object}';
}
List<NameValuePair> children(object) {
var trace = dart.stackTrace(object);
var entries = LinkedHashSet<NameValuePair>();
entries.add(NameValuePair(name: 'stackTrace', value: trace));
addInstanceMembers(object, entries);
addMetadataChildren(object, entries);
return entries.toList();
}
// Add an ObjectFormatter view underneath.
void addInstanceMembers(object, Set<NameValuePair> ret) {
ret.add(NameValuePair(
name: "[[instance members]]",
value: object,
config: JsonMLConfig.asObject));
}
}
class StackTraceFormatter implements Formatter {
accept(object, config) => object is StackTrace;
String preview(object) => 'StackTrace';
bool hasChildren(object) => true;
// Using the stack_trace formatting would be ideal, but adding the
// dependency or re-writing the code is too messy, so each line of the
// StackTrace will be added as its own child.
List<NameValuePair> children(object) => object
.toString()
.split('\n')
.map((line) => NameValuePair(
value: line.replaceFirst(RegExp(r'^\s+at\s'), ''), hideName: true))
.toList();
}
class ClassFormatter implements Formatter {
accept(object, config) => config == JsonMLConfig.asClass;
String preview(type) {
var implements = dart.getImplements(type);
var typeName = getTypeName(type);
if (implements != null) {
var typeNames = implements().map(getTypeName);
return '${typeName} implements ${typeNames.join(", ")}';
} else {
return typeName;
}
}
bool hasChildren(object) => true;
List<NameValuePair> children(type) {
// TODO(jacobr): add other entries describing the class such as
// implemented interfaces, and methods.
var ret = LinkedHashSet<NameValuePair>();
var staticProperties = Set<NameValuePair>();
var staticMethods = Set<NameValuePair>();
// Static fields and properties.
addPropertiesFromSignature(
dart.getStaticFields(type), staticProperties, type, false);
addPropertiesFromSignature(
dart.getStaticGetters(type), staticProperties, type, false);
// static methods.
addPropertiesFromSignature(
dart.getStaticMethods(type), staticMethods, type, false);
if (staticProperties.isNotEmpty || staticMethods.isNotEmpty) {
ret
..add(NameValuePair(value: '[[Static members]]', hideName: true))
..addAll(sortProperties(staticProperties))
..addAll(sortProperties(staticMethods));
}
// instance methods.
var instanceMethods = Set<NameValuePair>();
// Instance methods are defined on the prototype not the constructor object.
addPropertiesFromSignature(dart.getMethods(type), instanceMethods,
JS('', '#.prototype', type), false,
tagTypes: true);
if (instanceMethods.isNotEmpty) {
ret
..add(NameValuePair(value: '[[Instance Methods]]', hideName: true))
..addAll(sortProperties(instanceMethods));
}
var mixin = dart.getMixin(type);
if (mixin != null) {
// TODO(jmesserly): this can only be one value.
ret.add(NameValuePair(
name: '[[Mixins]]', value: HeritageClause('mixins', [mixin])));
}
var baseProto = JS('', '#.__proto__', type);
if (baseProto != null && !dart.isJsInterop(baseProto)) {
ret.add(NameValuePair(
name: "[[base class]]",
value: baseProto,
config: JsonMLConfig.asClass));
}
// TODO(jacobr): add back fields for named constructors.
return ret.toList();
}
}
class TypeFormatter implements Formatter {
accept(object, config) => object is Type;
String preview(object) => object.toString();
bool hasChildren(object) => false;
List<NameValuePair> children(object) => [];
}
typedef String StackTraceMapper(String stackTrace);
/// Hook for other parts of the SDK To use to map JS stack traces to Dart
/// stack traces.
///
/// Raw JS stack traces are used if $dartStackTraceUtility has not been
/// specified.
StackTraceMapper get stackTraceMapper {
var _util = JS('', r'#.$dartStackTraceUtility', dart.global_);
return _util != null ? JS('!', '#.mapper', _util) : null;
}
/// This entry point is automatically invoked by the code generated by
/// Dart Dev Compiler
registerDevtoolsFormatter() {
JS('', '#.devtoolsFormatters = [#]', dart.global_, _devtoolsFormatter);
}
// These methods are exposed here for debugger tests.
//
// TODO(jmesserly): these are not exports because there is existing code that
// calls into them from JS. Currently `dartdevc` always resolves exports at
// compile time, so there is no need to make exports available at runtime by
// copying properties. For that reason we cannot use re-export.
//
// If these methods are only for tests, we should move them here, or change the
// tests to call the methods directly on dart:_runtime.
List<String> getModuleNames() => dart.getModuleNames();
getModuleLibraries(String name) => dart.getModuleLibraries(name);