2bb32ec55a
Change-Id: Ib1afd38a7d3694c2e5ed591132f41b147f76d75a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427560 Reviewed-by: Stephen Adams <sra@google.com> Commit-Queue: Mayank Patke <fishythefish@google.com>
695 lines
20 KiB
Dart
695 lines
20 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.
|
|
|
|
/// Helper for creating HTML visualization of the source map information
|
|
/// generated by a [SourceMapProcessor].
|
|
|
|
library sourcemap.html.helper;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:math' as Math;
|
|
|
|
import 'package:compiler/src/io/source_file.dart';
|
|
import 'package:compiler/src/io/source_information.dart';
|
|
import 'package:compiler/src/js/js.dart' as js;
|
|
|
|
import 'colors.dart';
|
|
import 'sourcemap_helper.dart';
|
|
import 'sourcemap_html_templates.dart';
|
|
import 'html_parts.dart';
|
|
|
|
/// Truncate [input] to [length], adding '...' if truncated.
|
|
String truncate(String input, int length) {
|
|
if (input.length > length) {
|
|
return '${input.substring(0, length - 3)}...';
|
|
}
|
|
return input;
|
|
}
|
|
|
|
const int HUE_COUNT = 24;
|
|
|
|
/// Returns the [index]th color for visualization.
|
|
HSV toColor(int index) {
|
|
double h = 360.0 * (index % HUE_COUNT) / HUE_COUNT;
|
|
double v = 1.0;
|
|
double s = 0.5;
|
|
return HSV(h, s, v);
|
|
}
|
|
|
|
/// Return the CSS color value for the [index]th color.
|
|
String toColorCss(int index) {
|
|
return toColor(index).toCss;
|
|
}
|
|
|
|
/// Return the CSS color value for the [index]th span.
|
|
String toPattern(int index) {
|
|
/// Use gradient on spans to visually identify consecutive spans mapped to the
|
|
/// same source location.
|
|
HSV startColor = toColor(index);
|
|
HSV endColor = HSV(startColor.h, startColor.s + 0.4, startColor.v - 0.2);
|
|
return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})';
|
|
}
|
|
|
|
/// Return the html for the [index] line number. If [width] is provided, shorter
|
|
/// line numbers will be prefixed with spaces to match the width.
|
|
String lineNumber(
|
|
int index, {
|
|
int? width,
|
|
bool useNbsp = false,
|
|
String? className,
|
|
}) {
|
|
if (className == null) {
|
|
className = 'lineNumber';
|
|
}
|
|
String text = '${index + 1}';
|
|
String padding = useNbsp ? ' ' : ' ';
|
|
if (width != null && text.length < width) {
|
|
text = (padding * (width - text.length)) + text;
|
|
}
|
|
return '<span class="$className">$text$padding</span>';
|
|
}
|
|
|
|
/// Return the html escaped [text].
|
|
String escape(String text) {
|
|
return const HtmlEscape().convert(text);
|
|
}
|
|
|
|
/// Information needed to generate HTML for a single [SourceMapInfo].
|
|
class SourceMapHtmlInfo {
|
|
final SourceMapInfo sourceMapInfo;
|
|
final CodeProcessor codeProcessor;
|
|
final SourceLocationCollection sourceLocationCollection;
|
|
|
|
SourceMapHtmlInfo(
|
|
this.sourceMapInfo,
|
|
this.codeProcessor,
|
|
this.sourceLocationCollection,
|
|
);
|
|
|
|
@override
|
|
String toString() {
|
|
return sourceMapInfo.toString();
|
|
}
|
|
}
|
|
|
|
/// A collection of source locations.
|
|
///
|
|
/// Used to index source locations for visualization and linking.
|
|
class SourceLocationCollection {
|
|
List<SourceLocation> sourceLocations = [];
|
|
Map<SourceLocation, int> sourceLocationIndexMap;
|
|
|
|
SourceLocationCollection([SourceLocationCollection? parent])
|
|
: sourceLocationIndexMap = parent == null
|
|
? {}
|
|
: parent.sourceLocationIndexMap;
|
|
|
|
int registerSourceLocation(SourceLocation sourceLocation) {
|
|
return sourceLocationIndexMap.putIfAbsent(sourceLocation, () {
|
|
sourceLocations.add(sourceLocation);
|
|
return sourceLocationIndexMap.length;
|
|
});
|
|
}
|
|
|
|
int getIndex(SourceLocation sourceLocation) {
|
|
return sourceLocationIndexMap[sourceLocation]!;
|
|
}
|
|
}
|
|
|
|
abstract class CssColorScheme {
|
|
String singleLocationToCssColor(int id);
|
|
|
|
String multiLocationToCssColor(List<int> ids);
|
|
|
|
bool get showLocationAsSpan;
|
|
}
|
|
|
|
class PatternCssColorScheme implements CssColorScheme {
|
|
const PatternCssColorScheme();
|
|
|
|
@override
|
|
bool get showLocationAsSpan => true;
|
|
|
|
@override
|
|
String singleLocationToCssColor(int index) {
|
|
return "background:${toPattern(index)};";
|
|
}
|
|
|
|
@override
|
|
String multiLocationToCssColor(List<int> indices) {
|
|
StringBuffer sb = StringBuffer();
|
|
double delta = 100.0 / (indices.length);
|
|
double position = 0.0;
|
|
|
|
void addColor(String color) {
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
position += delta;
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
}
|
|
|
|
for (int index in indices) {
|
|
addColor('${toColorCss(index)}');
|
|
}
|
|
return 'background: linear-gradient(to right${sb}); '
|
|
'background-size: 10px 10px;';
|
|
}
|
|
}
|
|
|
|
class SingleColorScheme implements CssColorScheme {
|
|
const SingleColorScheme();
|
|
|
|
@override
|
|
bool get showLocationAsSpan => false;
|
|
|
|
@override
|
|
String singleLocationToCssColor(int index) {
|
|
return "background:${toColorCss(index)};";
|
|
}
|
|
|
|
@override
|
|
String multiLocationToCssColor(List<int> indices) {
|
|
StringBuffer sb = StringBuffer();
|
|
double delta = 100.0 / (indices.length);
|
|
double position = 0.0;
|
|
|
|
void addColor(String color) {
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
position += delta;
|
|
sb.write(', ${color} ${position.toInt()}%');
|
|
}
|
|
|
|
for (int index in indices) {
|
|
addColor('${toColorCss(index)}');
|
|
}
|
|
return 'background: linear-gradient(to bottom${sb}); '
|
|
'background-size: 10px 3px;';
|
|
}
|
|
}
|
|
|
|
/// Processor that computes the HTML representation of a block of JavaScript
|
|
/// code and collects the source locations mapped in the code.
|
|
class CodeProcessor {
|
|
int lineIndex = 0;
|
|
final String name;
|
|
int currentJsSourceOffset = 0;
|
|
final SourceLocationCollection collection;
|
|
final Map<int, List<SourceLocation>> codeLocations = {};
|
|
final CssColorScheme colorScheme;
|
|
|
|
CodeProcessor(
|
|
this.name,
|
|
this.collection, {
|
|
this.colorScheme = const PatternCssColorScheme(),
|
|
});
|
|
|
|
void addSourceLocation(int targetOffset, SourceLocation sourceLocation) {
|
|
codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation);
|
|
collection.registerSourceLocation(sourceLocation);
|
|
}
|
|
|
|
String convertToHtml(String text) {
|
|
List<Annotation> annotations = <Annotation>[];
|
|
codeLocations.forEach((int codeOffset, List<SourceLocation> locations) {
|
|
for (SourceLocation location in locations) {
|
|
annotations.add(
|
|
new Annotation(
|
|
collection.getIndex(location),
|
|
codeOffset,
|
|
location.shortText,
|
|
),
|
|
);
|
|
}
|
|
});
|
|
return convertAnnotatedCodeToHtml(
|
|
text,
|
|
annotations,
|
|
colorScheme: colorScheme,
|
|
elementScheme: HighlightLinkScheme(name),
|
|
windowSize: 3,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ElementScheme {
|
|
const ElementScheme();
|
|
|
|
String? getName(int id, Set<int> ids) => null;
|
|
String? getHref(int id, Set<int> ids) => null;
|
|
String? onClick(int id, Set<int> ids) => null;
|
|
String? onMouseOver(int id, Set<int> ids) => null;
|
|
String? onMouseOut(int id, Set<int> ids) => null;
|
|
}
|
|
|
|
class HighlightLinkScheme implements ElementScheme {
|
|
final String name;
|
|
|
|
HighlightLinkScheme(this.name);
|
|
|
|
@override
|
|
String getName(int id, Set<int> indices) {
|
|
return 'js$id';
|
|
}
|
|
|
|
@override
|
|
String getHref(int id, Set<int> indices) {
|
|
return "#${id}";
|
|
}
|
|
|
|
@override
|
|
String onClick(int id, Set<int> indices) {
|
|
return "show(\'$name\');";
|
|
}
|
|
|
|
@override
|
|
String onMouseOut(int id, Set<int> indices) {
|
|
return "highlight([]);";
|
|
}
|
|
|
|
@override
|
|
String onMouseOver(int id, Set<int> indices) {
|
|
String onmouseover = indices.map((i) => '\'$i\'').join(',');
|
|
return "highlight([${onmouseover}]);";
|
|
}
|
|
}
|
|
|
|
String convertAnnotatedCodeToHtml(
|
|
String code,
|
|
Iterable<Annotation> annotations, {
|
|
CssColorScheme colorScheme = const SingleColorScheme(),
|
|
required ElementScheme elementScheme,
|
|
required int windowSize,
|
|
}) {
|
|
StringBuffer htmlBuffer = StringBuffer();
|
|
List<CodeLine> lines = convertAnnotatedCodeToCodeLines(
|
|
code,
|
|
annotations,
|
|
windowSize: windowSize,
|
|
);
|
|
int? lineNoWidth;
|
|
if (lines.isNotEmpty) {
|
|
lineNoWidth = '${lines.last.lineNo + 1}'.length;
|
|
}
|
|
HtmlPrintContext context = HtmlPrintContext(
|
|
lineNoWidth: lineNoWidth,
|
|
getAnnotationData: createAnnotationDataFunction(
|
|
colorScheme: colorScheme,
|
|
elementScheme: elementScheme,
|
|
),
|
|
);
|
|
for (CodeLine line in lines) {
|
|
line.printHtmlOn(htmlBuffer, context);
|
|
}
|
|
return htmlBuffer.toString();
|
|
}
|
|
|
|
List<CodeLine> convertAnnotatedCodeToCodeLines(
|
|
String code,
|
|
Iterable<Annotation> annotations, {
|
|
int? startLine,
|
|
int? endLine,
|
|
int? windowSize,
|
|
Uri? uri,
|
|
}) {
|
|
List<CodeLine> lines = <CodeLine>[];
|
|
CodeLine? currentLine;
|
|
final List<Annotation> currentAnnotations = <Annotation>[];
|
|
int offset = 0;
|
|
int lineIndex = 0;
|
|
late final int firstLine;
|
|
late final int lastLine;
|
|
|
|
void addCode(String code) {
|
|
if (currentLine != null) {
|
|
currentLine!.codeBuffer.write(code);
|
|
currentLine!.codeParts.add(
|
|
new CodePart(currentAnnotations.toList(), code),
|
|
);
|
|
currentAnnotations.clear();
|
|
}
|
|
}
|
|
|
|
void addAnnotations(List<Annotation> annotations) {
|
|
currentAnnotations.addAll(annotations);
|
|
if (currentLine != null) {
|
|
currentLine!.annotations.addAll(annotations);
|
|
}
|
|
}
|
|
|
|
void beginLine(int currentOffset) {
|
|
lines.add(currentLine = CodeLine(lines.length, currentOffset, uri: uri));
|
|
}
|
|
|
|
void endCurrentLocation() {
|
|
if (currentAnnotations.isNotEmpty) {
|
|
addCode('');
|
|
}
|
|
}
|
|
|
|
void addSubstring(int until, {bool isFirst = false, bool isLast = false}) {
|
|
if (until <= offset) return;
|
|
if (offset >= code.length) return;
|
|
|
|
String substring = code.substring(offset, until);
|
|
bool first = true;
|
|
|
|
if (isLast) {
|
|
lastLine = lineIndex;
|
|
}
|
|
int localOffset = 0;
|
|
if (isFirst) {
|
|
beginLine(offset + localOffset);
|
|
}
|
|
for (String line in substring.split('\n')) {
|
|
if (!first) {
|
|
endCurrentLocation();
|
|
lineIndex++;
|
|
beginLine(offset + localOffset);
|
|
}
|
|
addCode(line);
|
|
first = false;
|
|
localOffset += line.length + 1;
|
|
}
|
|
if (isFirst) {
|
|
firstLine = lineIndex;
|
|
}
|
|
offset = until;
|
|
}
|
|
|
|
void insertAnnotations(List<Annotation> annotations) {
|
|
endCurrentLocation();
|
|
addAnnotations(annotations);
|
|
}
|
|
|
|
Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{};
|
|
for (Annotation annotation in annotations) {
|
|
annotationMap
|
|
.putIfAbsent(annotation.codeOffset, () => <Annotation>[])
|
|
.add(annotation);
|
|
}
|
|
|
|
bool first = true;
|
|
for (int codeOffset in annotationMap.keys.toList()..sort()) {
|
|
List<Annotation> annotationList = annotationMap[codeOffset]!;
|
|
addSubstring(codeOffset, isFirst: first);
|
|
insertAnnotations(annotationList);
|
|
first = false;
|
|
}
|
|
|
|
addSubstring(code.length, isFirst: first, isLast: true);
|
|
endCurrentLocation();
|
|
|
|
int start = startLine ?? 0;
|
|
int end = endLine ?? lines.length - 1;
|
|
if (lastLine == 0) lastLine = firstLine;
|
|
if (windowSize != null) {
|
|
start = Math.max(firstLine - windowSize, start);
|
|
end = Math.min(lastLine + windowSize, end);
|
|
}
|
|
return lines.sublist(start, end);
|
|
}
|
|
|
|
/// Computes the HTML representation for a collection of JavaScript code blocks.
|
|
String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
|
StringBuffer jsCodeBuffer = StringBuffer();
|
|
for (SourceMapHtmlInfo info in infoList) {
|
|
String name = info.sourceMapInfo.name!;
|
|
String html = info.codeProcessor.convertToHtml(info.sourceMapInfo.code);
|
|
String onclick = 'show(\'$name\');';
|
|
jsCodeBuffer.write(
|
|
'<h3 onclick="$onclick">JS code for: ${escape(name)}</h3>\n',
|
|
);
|
|
jsCodeBuffer.write('''
|
|
<pre>
|
|
$html
|
|
</pre>
|
|
''');
|
|
}
|
|
return jsCodeBuffer.toString();
|
|
}
|
|
|
|
/// Computes the HTML representation of the source mapping information for a
|
|
/// collection of JavaScript code blocks.
|
|
String computeJsTraceHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
|
StringBuffer jsTraceBuffer = StringBuffer();
|
|
for (SourceMapHtmlInfo info in infoList) {
|
|
String name = info.sourceMapInfo.name!;
|
|
String jsTrace = computeJsTraceHtmlPart(
|
|
info.sourceMapInfo.codePoints,
|
|
info.sourceLocationCollection,
|
|
);
|
|
jsTraceBuffer.write('''
|
|
<div name="$name" class="js-trace-buffer" style="display:none;">
|
|
<h3>Trace for: ${escape(name)}</h3>
|
|
$jsTrace
|
|
</div>
|
|
''');
|
|
}
|
|
return jsTraceBuffer.toString();
|
|
}
|
|
|
|
/// Computes the HTML information for the [info].
|
|
SourceMapHtmlInfo createHtmlInfo(
|
|
SourceLocationCollection collection,
|
|
SourceMapInfo info,
|
|
) {
|
|
String name = info.name!;
|
|
SourceLocationCollection subcollection = SourceLocationCollection(collection);
|
|
CodeProcessor codeProcessor = CodeProcessor(name, subcollection);
|
|
for (js.Node node in info.nodeMap.nodes) {
|
|
info.nodeMap[node]!.forEach((
|
|
int targetOffset,
|
|
List<SourceLocation> sourceLocations,
|
|
) {
|
|
for (SourceLocation sourceLocation in sourceLocations) {
|
|
codeProcessor.addSourceLocation(targetOffset, sourceLocation);
|
|
}
|
|
});
|
|
}
|
|
return SourceMapHtmlInfo(info, codeProcessor, subcollection);
|
|
}
|
|
|
|
/// Outputs a HTML file in [jsMapHtmlUri] containing an interactive
|
|
/// visualization of the source mapping information in [infoList] computed
|
|
/// with the [sourceMapProcessor].
|
|
void createTraceSourceMapHtml(
|
|
Uri jsMapHtmlUri,
|
|
SourceMapProcessor sourceMapProcessor,
|
|
Iterable<SourceMapInfo> infoList,
|
|
) {
|
|
SourceFileManager sourceFileManager = sourceMapProcessor.sourceFileManager;
|
|
SourceLocationCollection collection = SourceLocationCollection();
|
|
List<SourceMapHtmlInfo> htmlInfoList = <SourceMapHtmlInfo>[];
|
|
for (SourceMapInfo info in infoList) {
|
|
htmlInfoList.add(createHtmlInfo(collection, info));
|
|
}
|
|
|
|
String jsCode = computeJsHtml(htmlInfoList);
|
|
String dartCode = computeDartHtml(sourceFileManager, htmlInfoList);
|
|
|
|
String jsTraceHtml = computeJsTraceHtml(htmlInfoList);
|
|
outputJsDartTrace(jsMapHtmlUri, jsCode, dartCode, jsTraceHtml);
|
|
print('Trace source map html generated: $jsMapHtmlUri');
|
|
}
|
|
|
|
/// Computes the HTML representation for the Dart code snippets referenced in
|
|
/// [infoList].
|
|
String computeDartHtml(
|
|
SourceFileManager sourceFileManager,
|
|
Iterable<SourceMapHtmlInfo> infoList,
|
|
) {
|
|
StringBuffer dartCodeBuffer = StringBuffer();
|
|
for (SourceMapHtmlInfo info in infoList) {
|
|
dartCodeBuffer.write(
|
|
computeDartHtmlPart(
|
|
info.sourceMapInfo.name!,
|
|
sourceFileManager,
|
|
info.sourceLocationCollection,
|
|
),
|
|
);
|
|
}
|
|
return dartCodeBuffer.toString();
|
|
}
|
|
|
|
/// Computes the HTML representation for the Dart code snippets in [collection].
|
|
String computeDartHtmlPart(
|
|
String name,
|
|
SourceFileManager sourceFileManager,
|
|
SourceLocationCollection collection, {
|
|
bool showAsBlock = false,
|
|
}) {
|
|
const int windowSize = 3;
|
|
StringBuffer dartCodeBuffer = StringBuffer();
|
|
Map<Uri, Map<int, List<SourceLocation>>> sourceLocationMap = {};
|
|
collection.sourceLocations.forEach((SourceLocation sourceLocation) {
|
|
if (sourceLocation.sourceUri == null) return;
|
|
Map<int, List<SourceLocation>> uriMap = sourceLocationMap.putIfAbsent(
|
|
sourceLocation.sourceUri!,
|
|
() => {},
|
|
);
|
|
List<SourceLocation> lineList = uriMap.putIfAbsent(
|
|
sourceLocation.line - 1,
|
|
() => [],
|
|
);
|
|
lineList.add(sourceLocation);
|
|
});
|
|
sourceLocationMap.forEach((Uri uri, Map<int, List<SourceLocation>> uriMap) {
|
|
SourceFile? sourceFile = sourceFileManager.getSourceFile(uri);
|
|
if (sourceFile == null) {
|
|
print('No source file for $uri');
|
|
return;
|
|
}
|
|
StringBuffer codeBuffer = StringBuffer();
|
|
|
|
int? firstLineIndex;
|
|
int? lastLineIndex;
|
|
List<int> lineIndices = uriMap.keys.toList()..sort();
|
|
int? lineNoWidth;
|
|
if (lineIndices.isNotEmpty) {
|
|
lineNoWidth = '${lineIndices.last + windowSize + 1}'.length;
|
|
}
|
|
|
|
void flush() {
|
|
if (firstLineIndex != null && lastLineIndex != null) {
|
|
dartCodeBuffer.write(
|
|
'<h4>${uri.pathSegments.last}, '
|
|
'${firstLineIndex! - windowSize + 1}-'
|
|
'${lastLineIndex! + windowSize + 1}'
|
|
'</h4>\n',
|
|
);
|
|
dartCodeBuffer.write('<pre>\n');
|
|
dartCodeBuffer.write('<p class="line">');
|
|
for (
|
|
int line = firstLineIndex! - windowSize;
|
|
line < firstLineIndex!;
|
|
line++
|
|
) {
|
|
if (line >= 0) {
|
|
dartCodeBuffer.write('</p><p class="line">');
|
|
dartCodeBuffer.write(lineNumber(line, width: lineNoWidth));
|
|
dartCodeBuffer.write(sourceFile.kernelSource.getTextLine(line + 1));
|
|
}
|
|
}
|
|
dartCodeBuffer.write(codeBuffer);
|
|
for (
|
|
int line = lastLineIndex! + 1;
|
|
line <= lastLineIndex! + windowSize;
|
|
line++
|
|
) {
|
|
if (line < sourceFile.lines) {
|
|
dartCodeBuffer.write('</p><p class="line">');
|
|
dartCodeBuffer.write(lineNumber(line, width: lineNoWidth));
|
|
dartCodeBuffer.write(sourceFile.kernelSource.getTextLine(line + 1));
|
|
}
|
|
}
|
|
dartCodeBuffer.write('</p>');
|
|
dartCodeBuffer.write('</pre>\n');
|
|
firstLineIndex = null;
|
|
lastLineIndex = null;
|
|
}
|
|
codeBuffer.clear();
|
|
}
|
|
|
|
lineIndices.forEach((int lineIndex) {
|
|
List<SourceLocation> locations = uriMap[lineIndex]!;
|
|
if (lastLineIndex != null &&
|
|
lastLineIndex! + windowSize * 4 < lineIndex) {
|
|
flush();
|
|
}
|
|
if (firstLineIndex == null) {
|
|
firstLineIndex = lineIndex;
|
|
} else {
|
|
for (int line = lastLineIndex! + 1; line < lineIndex; line++) {
|
|
codeBuffer.write('</p><p class="line">');
|
|
codeBuffer.write(lineNumber(line, width: lineNoWidth));
|
|
codeBuffer.write(sourceFile.kernelSource.getTextLine(line + 1));
|
|
}
|
|
}
|
|
String line = sourceFile.kernelSource.getTextLine(lineIndex + 1)!;
|
|
locations.sort((a, b) => a.offset.compareTo(b.offset));
|
|
for (int i = 0; i < locations.length; i++) {
|
|
SourceLocation sourceLocation = locations[i];
|
|
int index = collection.getIndex(sourceLocation);
|
|
int start = sourceLocation.column - 1;
|
|
int end = line.length;
|
|
if (i + 1 < locations.length) {
|
|
end = locations[i + 1].column - 1;
|
|
}
|
|
if (i == 0) {
|
|
codeBuffer.write('</p><p class="line">');
|
|
codeBuffer.write(lineNumber(lineIndex, width: lineNoWidth));
|
|
codeBuffer.write(line.substring(0, start));
|
|
}
|
|
codeBuffer.write(
|
|
'<a name="${index}" style="background:${toPattern(index)};" '
|
|
'title="[${lineIndex + 1},${start + 1}]" '
|
|
'onmouseover="highlight(\'$index\');" '
|
|
'onmouseout="highlight();">',
|
|
);
|
|
codeBuffer.write(line.substring(start, end));
|
|
codeBuffer.write('</a>');
|
|
}
|
|
lastLineIndex = lineIndex;
|
|
});
|
|
|
|
flush();
|
|
});
|
|
String display = showAsBlock ? 'block' : 'none';
|
|
return '''
|
|
<div name="$name" class="dart-buffer" style="display:$display;">
|
|
<h3>Dart code for: ${escape(name)}</h3>
|
|
${dartCodeBuffer}
|
|
</div>''';
|
|
}
|
|
|
|
/// Computes a HTML visualization of the [codePoints].
|
|
String computeJsTraceHtmlPart(
|
|
List<CodePoint> codePoints,
|
|
SourceLocationCollection collection,
|
|
) {
|
|
StringBuffer buffer = StringBuffer();
|
|
buffer.write('<table style="width:100%;">');
|
|
buffer.write(
|
|
'<tr><th>Node kind</th><th>JS code @ offset</th>'
|
|
'<th>Dart code @ mapped location</th><th>file:position:name</th></tr>',
|
|
);
|
|
codePoints.forEach((CodePoint codePoint) {
|
|
String jsCode = truncate(codePoint.jsCode, 50);
|
|
if (codePoint.sourceLocation != null) {
|
|
int index = collection.getIndex(codePoint.sourceLocation!);
|
|
String style = '';
|
|
if (!codePoint.isMissing) {
|
|
style = 'style="background:${toColorCss(index)};" ';
|
|
|
|
buffer.write(
|
|
'<tr $style'
|
|
'name="trace$index" '
|
|
'onmouseover="highlight([${index}]);"'
|
|
'onmouseout="highlight([]);">',
|
|
);
|
|
} else {
|
|
buffer.write('<tr>');
|
|
print('${codePoint.sourceLocation} not found in ');
|
|
collection.sourceLocationIndexMap.keys
|
|
.where((l) => l.sourceUri == codePoint.sourceLocation!.sourceUri)
|
|
.forEach((l) => print(' $l'));
|
|
}
|
|
} else {
|
|
buffer.write('<tr>');
|
|
}
|
|
buffer.write('<td>${codePoint.kind}</td>');
|
|
buffer.write('<td class="code">${codePoint.targetOffset}:${jsCode}</td>');
|
|
if (codePoint.sourceLocation == null) {
|
|
//buffer.write('<td></td>');
|
|
} else {
|
|
String dartCode = truncate(codePoint.dartCode!, 50);
|
|
buffer.write('<td class="code">${dartCode}</td>');
|
|
buffer.write('<td>${escape(codePoint.sourceLocation!.shortText)}</td>');
|
|
}
|
|
buffer.write('</tr>');
|
|
});
|
|
buffer.write('</table>');
|
|
|
|
return buffer.toString();
|
|
}
|