| Index: tests/compiler/dart2js/sourcemaps/diff.dart
|
| diff --git a/tests/compiler/dart2js/sourcemaps/diff.dart b/tests/compiler/dart2js/sourcemaps/diff.dart
|
| index 6fb4468bd59ea3d617e9b7980d5e58cffb2ab61f..4fd31c161094ca8bd09716b25017dcb3e5bcfa67 100644
|
| --- a/tests/compiler/dart2js/sourcemaps/diff.dart
|
| +++ b/tests/compiler/dart2js/sourcemaps/diff.dart
|
| @@ -9,6 +9,7 @@ import 'package:compiler/src/io/source_file.dart';
|
| import 'html_parts.dart';
|
| import 'output_structure.dart';
|
| import 'sourcemap_helper.dart';
|
| +import 'sourcemap_html_helper.dart';
|
|
|
| enum DiffKind {
|
| UNMATCHED,
|
| @@ -16,26 +17,97 @@ enum DiffKind {
|
| IDENTICAL,
|
| }
|
|
|
| +/// Id for an output column.
|
| +class DiffColumn {
|
| + final String type;
|
| + final int index;
|
| +
|
| + const DiffColumn(this.type, [this.index]);
|
| +
|
| + int get hashCode => type.hashCode * 19 + index.hashCode * 23;
|
| +
|
| + bool operator ==(other) {
|
| + if (identical(this, other)) return true;
|
| + if (other is! DiffColumn) return false;
|
| + return type == other.type && index == other.index;
|
| + }
|
| +
|
| + String toString() => '$type${index != null ? index : ''}';
|
| +}
|
| +
|
| +/// A block of code in an output column.
|
| +abstract class DiffColumnBlock {
|
| + void printHtmlOn(StringBuffer htmlBuffer, HtmlPrintContext context);
|
| +}
|
| +
|
| +/// A block consisting of pure HTML parts.
|
| +class PartsColumnBlock extends DiffColumnBlock {
|
| + final List<HtmlPart> parts;
|
| +
|
| + PartsColumnBlock(this.parts);
|
| +
|
| + void printHtmlOn(StringBuffer htmlBuffer, HtmlPrintContext context) {
|
| + if (parts.isNotEmpty) {
|
| + for (HtmlPart part in parts) {
|
| + part.printHtmlOn(htmlBuffer, context);
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +/// A block consisting of line-per-line JavaScript and source mapped Dart code.
|
| +class CodeLinesColumnBlock extends DiffColumnBlock {
|
| + final List<CodeLine> jsCodeLines;
|
| + final Map<CodeLine, List<CodeLine>> jsToDartMap;
|
| +
|
| + CodeLinesColumnBlock(this.jsCodeLines, this.jsToDartMap);
|
| +
|
| + void printHtmlOn(StringBuffer htmlBuffer, HtmlPrintContext context) {
|
| + if (jsCodeLines.isNotEmpty) {
|
| + htmlBuffer.write('<table style="width:100%">');
|
| + for (CodeLine codeLine in jsCodeLines) {
|
| + htmlBuffer.write('<tr><td class="${ClassNames.innerCell}">');
|
| + codeLine.printHtmlOn(htmlBuffer, context);
|
| + htmlBuffer.write(
|
| + '</td><td '
|
| + 'class="${ClassNames.innerCell} ${ClassNames.sourceMapped}">');
|
| + List<CodeLine> lines = jsToDartMap[codeLine];
|
| + if (lines != null) {
|
| + for (CodeLine line in lines) {
|
| + line.printHtmlOn(htmlBuffer,
|
| + context.from(includeAnnotation: (a) {
|
| + CodeLineAnnotation annotation = a.data;
|
| + return annotation.annotationType.isSourceMapped;
|
| + }));
|
| + }
|
| + }
|
| + htmlBuffer.write('</td></tr>');
|
| + }
|
| + htmlBuffer.write('</table>');
|
| + }
|
| + }
|
| +}
|
| +
|
| /// A list of columns that should align in output.
|
| class DiffBlock {
|
| final DiffKind kind;
|
| - List<List<HtmlPart>> columns = <List<HtmlPart>>[];
|
| + Map<DiffColumn, DiffColumnBlock> _columns = <DiffColumn, DiffColumnBlock>{};
|
|
|
| DiffBlock(this.kind);
|
|
|
| - void addColumn(int index, List<HtmlPart> lines) {
|
| - if (index >= columns.length) {
|
| - columns.length = index + 1;
|
| - }
|
| - columns[index] = lines;
|
| + void addColumnBlock(DiffColumn column, DiffColumnBlock block) {
|
| + _columns[column] = block;
|
| }
|
|
|
| - List<HtmlPart> getColumn(int index) {
|
| - List<HtmlPart> lines;
|
| - if (index < columns.length) {
|
| - lines = columns[index];
|
| + Iterable<DiffColumn> get columns => _columns.keys;
|
| +
|
| + void printHtmlOn(DiffColumn column,
|
| + StringBuffer htmlBuffer,
|
| + HtmlPrintContext context) {
|
| + DiffColumnBlock block = _columns[column];
|
| + if (block != null) {
|
| + block.printHtmlOn(htmlBuffer, context);
|
| }
|
| - return lines != null ? lines : const <HtmlPart>[];
|
| }
|
| }
|
|
|
| @@ -176,13 +248,47 @@ class DiffCreator {
|
| : this.structures = structures,
|
| this.inputLines = structures.map((s) => s.lines).toList();
|
|
|
| - CodeSource codeSourceFromEntities(Iterable<OutputEntity> entities) {
|
| + /// Compute [CodeSource]s defined by [entities].
|
| + Iterable<CodeSource> codeSourceFromEntities(Iterable<OutputEntity> entities) {
|
| + Set<CodeSource> sources = new Set<CodeSource>();
|
| for (OutputEntity entity in entities) {
|
| if (entity.codeSource != null) {
|
| - return entity.codeSource;
|
| + sources.add(entity.codeSource);
|
| + }
|
| + }
|
| + return sources;
|
| + }
|
| +
|
| + /// Create a block with the code from [codeSources]. The [CodeSource]s in
|
| + /// [mainSources] are tagged as original code sources, the rest as inlined
|
| + /// code sources.
|
| + DiffColumnBlock codeLinesFromCodeSources(
|
| + Iterable<CodeSource> mainSources,
|
| + Iterable<CodeSource> codeSources) {
|
| + List<HtmlPart> parts = <HtmlPart>[];
|
| + for (CodeSource codeSource in codeSources) {
|
| + //parts.addAll(codeLinesFromCodeSource(codeSource));
|
| + String className =
|
| + mainSources.contains(codeSource)
|
| + ? ClassNames.originalDart : ClassNames.inlinedDart;
|
| + parts.add(
|
| + new TagPart('div',
|
| + properties: {'class': className},
|
| + content: codeLinesFromCodeSource(codeSource)));
|
| + }
|
| + return new PartsColumnBlock(parts);
|
| + }
|
| +
|
| + /// Adds all [CodeSource]s used in [dartCodeLines] to [codeSourceSet].
|
| + void collectCodeSources(Set<CodeSource> codeSourceSet,
|
| + Map<CodeLine, List<CodeLine>> dartCodeLines) {
|
| + for (List<CodeLine> codeLines in dartCodeLines.values) {
|
| + for (CodeLine dartCodeLine in codeLines) {
|
| + if (dartCodeLine.lineAnnotation != null) {
|
| + codeSourceSet.add(dartCodeLine.lineAnnotation);
|
| + }
|
| }
|
| }
|
| - return null;
|
| }
|
|
|
| /// Checks that lines are added in sequence without gaps or duplicates.
|
| @@ -210,13 +316,30 @@ class DiffCreator {
|
| /// Creates a block containing the code lines in [range] from input number
|
| /// [index]. If [codeSource] is provided, the block will contain a
|
| /// corresponding Dart code column.
|
| - void handleSkew(int index, Interval range, [CodeSource codeSource]) {
|
| + void handleSkew(
|
| + int index,
|
| + Interval range,
|
| + [Iterable<CodeSource> mainCodeSources = const <CodeSource>[]]) {
|
| + if (range.isEmpty) return;
|
| +
|
| + Set<CodeSource> codeSources = new Set<CodeSource>();
|
| + codeSources.addAll(mainCodeSources);
|
| +
|
| DiffBlock block = new DiffBlock(DiffKind.UNMATCHED);
|
| checkLineInvariant(index, range);
|
| - block.addColumn(index, inputLines[index].sublist(range.from, range.to));
|
| - if (codeSource != null) {
|
| - block.addColumn(2,
|
| - codeLinesFromCodeSource(sourceFileManager, codeSource));
|
| + List<CodeLine> jsCodeLines =
|
| + inputLines[index].sublist(range.from, range.to);
|
| + Map<CodeLine, List<CodeLine>> dartCodeLines =
|
| + dartCodeLinesFromJsCodeLines(jsCodeLines);
|
| + block.addColumnBlock(
|
| + new DiffColumn('js', index),
|
| + new CodeLinesColumnBlock(jsCodeLines, dartCodeLines));
|
| + collectCodeSources(codeSources, dartCodeLines);
|
| +
|
| + if (codeSources.isNotEmpty) {
|
| + block.addColumnBlock(
|
| + const DiffColumn('dart'),
|
| + codeLinesFromCodeSources(mainCodeSources, codeSources));
|
| }
|
| blocks.add(block);
|
| }
|
| @@ -224,21 +347,38 @@ class DiffCreator {
|
| /// Create a block containing the code lines in [ranges] from the
|
| /// corresponding JavaScript inputs. If [codeSource] is provided, the block
|
| /// will contain a corresponding Dart code column.
|
| - void addLines(DiffKind kind, List<Interval> ranges, [CodeSource codeSource]) {
|
| + void addLines(
|
| + DiffKind kind,
|
| + List<Interval> ranges,
|
| + [Iterable<CodeSource> mainCodeSources = const <CodeSource>[]]) {
|
| + if (ranges.every((range) => range.isEmpty)) return;
|
| +
|
| + Set<CodeSource> codeSources = new Set<CodeSource>();
|
| + codeSources.addAll(mainCodeSources);
|
| +
|
| DiffBlock block = new DiffBlock(kind);
|
| for (int i = 0; i < ranges.length; i++) {
|
| checkLineInvariant(i, ranges[i]);
|
| - block.addColumn(i, inputLines[i].sublist(ranges[i].from, ranges[i].to));
|
| + List<CodeLine> jsCodeLines =
|
| + inputLines[i].sublist(ranges[i].from, ranges[i].to);
|
| + Map<CodeLine, List<CodeLine>> dartCodeLines =
|
| + dartCodeLinesFromJsCodeLines(jsCodeLines);
|
| + block.addColumnBlock(
|
| + new DiffColumn('js', i),
|
| + new CodeLinesColumnBlock(jsCodeLines, dartCodeLines));
|
| + collectCodeSources(codeSources, dartCodeLines);
|
| }
|
| - if (codeSource != null) {
|
| - block.addColumn(2,
|
| - codeLinesFromCodeSource(sourceFileManager, codeSource));
|
| + if (codeSources.isNotEmpty) {
|
| + block.addColumnBlock(const DiffColumn('dart'),
|
| + codeLinesFromCodeSources(mainCodeSources, codeSources));
|
| }
|
| blocks.add(block);
|
| }
|
|
|
| /// Merge the code lines in [range1] and [range2] of the corresponding input.
|
| void addRaw(Interval range1, Interval range2) {
|
| + if (range1.isEmpty && range2.isEmpty) return;
|
| +
|
| match(a, b) => a.code == b.code;
|
|
|
| List<Interval> currentMatchedIntervals;
|
| @@ -413,32 +553,250 @@ class DiffCreator {
|
|
|
| return blocks;
|
| }
|
| -}
|
|
|
| -/// Creates html lines for code lines in [codeSource]. [sourceFileManager] is
|
| -/// used to read that text from the source URIs.
|
| -List<HtmlPart> codeLinesFromCodeSource(
|
| - SourceFileManager sourceFileManager,
|
| - CodeSource codeSource) {
|
| - List<HtmlPart> lines = <HtmlPart>[];
|
| - SourceFile sourceFile = sourceFileManager.getSourceFile(codeSource.uri);
|
| - String elementName = codeSource.name;
|
| - HtmlLine line = new HtmlLine();
|
| - line.htmlParts.add(new ConstHtmlPart('<span class="comment">'));
|
| - line.htmlParts.add(new HtmlText(
|
| - '${elementName}: ${sourceFile.filename}'));
|
| - line.htmlParts.add(new ConstHtmlPart('</span>'));
|
| - lines.add(line);
|
| - if (codeSource.begin != null) {
|
| - int startLine = sourceFile.getLine(codeSource.begin);
|
| - int endLine = sourceFile.getLine(codeSource.end);
|
| - for (int lineNo = startLine; lineNo <= endLine; lineNo++) {
|
| - String text = sourceFile.getLineText(lineNo);
|
| - CodeLine codeLine = new CodeLine(lineNo, sourceFile.getOffset(lineNo, 0));
|
| - codeLine.codeBuffer.write(text);
|
| - codeLine.htmlParts.add(new HtmlText(text));
|
| - lines.add(codeLine);
|
| + /// Creates html lines for code lines in [codeSource]. The [sourceFileManager]
|
| + /// is used to read that text from the source URIs.
|
| + List<HtmlPart> codeLinesFromCodeSource(CodeSource codeSource) {
|
| + List<HtmlPart> lines = <HtmlPart>[];
|
| + SourceFile sourceFile = sourceFileManager.getSourceFile(codeSource.uri);
|
| + String elementName = codeSource.name;
|
| + HtmlLine line = new HtmlLine();
|
| + line.htmlParts.add(new ConstHtmlPart('<span class="comment">'));
|
| + line.htmlParts.add(new HtmlText(
|
| + '${elementName}: ${sourceFile.filename}'));
|
| + line.htmlParts.add(new ConstHtmlPart('</span>'));
|
| + lines.add(line);
|
| + if (codeSource.begin != null) {
|
| + int startLine = sourceFile.getLine(codeSource.begin);
|
| + int endLine = sourceFile.getLine(codeSource.end) + 1;
|
| + for (CodeLine codeLine in convertAnnotatedCodeToCodeLines(
|
| + sourceFile.slowText(),
|
| + const <Annotation>[],
|
| + startLine: startLine,
|
| + endLine: endLine)) {
|
| + codeLine.lineAnnotation = codeSource;
|
| + lines.add(codeLine);
|
| + }
|
| + }
|
| + return lines;
|
| + }
|
| +
|
| + /// Creates a map from JavaScript [CodeLine]s in [jsCodeLines] to the Dart
|
| + /// [CodeLine]s references in the source information.
|
| + Map<CodeLine, List<CodeLine>> dartCodeLinesFromJsCodeLines(
|
| + List<CodeLine> jsCodeLines) {
|
| + Map<CodeLine, Interval> codeLineInterval = <CodeLine, Interval>{};
|
| + Map<CodeLine, List<CodeLine>> jsToDartMap = <CodeLine, List<CodeLine>>{};
|
| + List<Annotation> annotations = <Annotation>[];
|
| + Uri currentUri;
|
| + Interval interval;
|
| +
|
| + Map<Uri, Set<CodeSource>> codeSourceMap = <Uri, Set<CodeSource>>{};
|
| +
|
| + for (CodeLine jsCodeLine in jsCodeLines) {
|
| + for (Annotation annotation in jsCodeLine.annotations) {
|
| + CodeLineAnnotation codeLineAnnotation = annotation.data;
|
| + for (CodeSource codeSource in codeLineAnnotation.codeSources) {
|
| + codeSourceMap.putIfAbsent(codeSource.uri,
|
| + () => new Set<CodeSource>()).add(codeSource);
|
| + }
|
| + }
|
| + }
|
| +
|
| + void flush() {
|
| + if (currentUri == null) return;
|
| +
|
| + Set<CodeSource> codeSources = codeSourceMap[currentUri];
|
| + SourceFile sourceFile = sourceFileManager.getSourceFile(currentUri);
|
| + List<CodeLine> annotatedDartCodeLines =
|
| + convertAnnotatedCodeToCodeLines(
|
| + sourceFile.slowText(),
|
| + annotations,
|
| + startLine: interval.from,
|
| + endLine: interval.to,
|
| + uri: currentUri);
|
| + if (codeSources != null) {
|
| + CodeSource currentCodeSource;
|
| + Interval currentLineInterval;
|
| + for (CodeLine dartCodeLine in annotatedDartCodeLines) {
|
| + if (currentCodeSource == null ||
|
| + !currentLineInterval.contains(dartCodeLine.lineNo)) {
|
| + currentCodeSource = null;
|
| + for (CodeSource codeSource in codeSources) {
|
| + Interval interval = new Interval(
|
| + sourceFile.getLine(codeSource.begin),
|
| + sourceFile.getLine(codeSource.end) + 1);
|
| + if (interval.contains(dartCodeLine.lineNo)) {
|
| + currentCodeSource = codeSource;
|
| + currentLineInterval = interval;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + if (currentCodeSource != null) {
|
| + dartCodeLine.lineAnnotation = currentCodeSource;
|
| + }
|
| + }
|
| + }
|
| +
|
| + int index = 0;
|
| + for (CodeLine jsCodeLine in codeLineInterval.keys) {
|
| + List<CodeLine> dartCodeLines =
|
| + jsToDartMap.putIfAbsent(jsCodeLine, () => <CodeLine>[]);
|
| + if (dartCodeLines.isEmpty && index < annotatedDartCodeLines.length) {
|
| + dartCodeLines.add(annotatedDartCodeLines[index++]);
|
| + }
|
| + }
|
| + while (index < annotatedDartCodeLines.length) {
|
| + jsToDartMap[codeLineInterval.keys.last].add(
|
| + annotatedDartCodeLines[index++]);
|
| + }
|
| +
|
| + currentUri = null;
|
| }
|
| +
|
| + void restart(CodeLine codeLine, CodeLocation codeLocation, int line) {
|
| + flush();
|
| +
|
| + currentUri = codeLocation.uri;
|
| + interval = new Interval(line, line + 1);
|
| + annotations = <Annotation>[];
|
| + codeLineInterval.clear();
|
| + codeLineInterval[codeLine] = interval;
|
| + }
|
| +
|
| + for (CodeLine jsCodeLine in jsCodeLines) {
|
| + for (Annotation annotation in jsCodeLine.annotations) {
|
| + CodeLineAnnotation codeLineAnnotation = annotation.data;
|
| +
|
| + for (CodeLocation location in codeLineAnnotation.codeLocations) {
|
| + SourceFile sourceFile = sourceFileManager.getSourceFile(location.uri);
|
| + int line = sourceFile.getLine(location.offset);
|
| + if (currentUri != location.uri) {
|
| + restart(jsCodeLine, location, line);
|
| + } else if (interval.inWindow(line, windowSize: 2)) {
|
| + interval = interval.include(line);
|
| + codeLineInterval[jsCodeLine] = interval;
|
| + } else {
|
| + restart(jsCodeLine, location, line);
|
| + }
|
| +
|
| + annotations.add(new Annotation(
|
| + codeLineAnnotation.annotationType,
|
| + location.offset,
|
| + 'id=${codeLineAnnotation.annotationId}',
|
| + data: codeLineAnnotation));
|
| + }
|
| + }
|
| + }
|
| + flush();
|
| + return jsToDartMap;
|
| + }
|
| +}
|
| +
|
| +const DiffColumn column_js0 = const DiffColumn('js', 0);
|
| +const DiffColumn column_js1 = const DiffColumn('js', 1);
|
| +const DiffColumn column_dart = const DiffColumn('dart');
|
| +
|
| +class ClassNames {
|
| + static String column(DiffColumn column) => 'column_${column}';
|
| + static String identical(bool alternate) =>
|
| + 'identical${alternate ? '1' : '2'}';
|
| + static String corresponding(bool alternate) =>
|
| + 'corresponding${alternate ? '1' : '2'}';
|
| +
|
| + static const String buttons = 'buttons';
|
| + static const String comment = 'comment';
|
| + static const String header = 'header';
|
| + static const String headerTable = 'header_table';
|
| + static const String headerColumn = 'header_column';
|
| + static const String legend = 'legend';
|
| + static const String table = 'table';
|
| +
|
| + static const String cell = 'cell';
|
| + static const String innerCell = 'inner_cell';
|
| +
|
| + static const String originalDart = 'main_dart';
|
| + static const String inlinedDart = 'inlined_dart';
|
| +
|
| + static const String line = 'line';
|
| + static const String lineNumber = 'line_number';
|
| + static String colored(int index) => 'colored${index}';
|
| +
|
| + static const String withSourceInfo = 'with_source_info';
|
| + static const String withoutSourceInfo = 'without_source_info';
|
| + static const String additionalSourceInfo = 'additional_source_info';
|
| + static const String unusedSourceInfo = 'unused_source_info';
|
| +
|
| + static const String sourceMapped = 'source_mapped';
|
| + static const String sourceMapping = 'source_mapping';
|
| + static String sourceMappingIndex(int index) => 'source_mapping${index}';
|
| +
|
| + static const String markers = 'markers';
|
| + static const String marker = 'marker';
|
| +}
|
| +
|
| +class AnnotationType {
|
| + static const WITH_SOURCE_INFO =
|
| + const AnnotationType(0, ClassNames.withSourceInfo, true);
|
| + static const WITHOUT_SOURCE_INFO =
|
| + const AnnotationType(1, ClassNames.withoutSourceInfo, false);
|
| + static const ADDITIONAL_SOURCE_INFO =
|
| + const AnnotationType(2, ClassNames.additionalSourceInfo, true);
|
| + static const UNUSED_SOURCE_INFO =
|
| + const AnnotationType(3, ClassNames.unusedSourceInfo, false);
|
| +
|
| + final int index;
|
| + final String className;
|
| + final bool isSourceMapped;
|
| +
|
| + const AnnotationType(this.index, this.className, this.isSourceMapped);
|
| +
|
| + static const List<AnnotationType> values = const <AnnotationType>[
|
| + WITH_SOURCE_INFO,
|
| + WITHOUT_SOURCE_INFO,
|
| + ADDITIONAL_SOURCE_INFO,
|
| + UNUSED_SOURCE_INFO];
|
| +}
|
| +
|
| +class CodeLineAnnotation {
|
| + final int annotationId;
|
| + final AnnotationType annotationType;
|
| + final List<CodeLocation> codeLocations;
|
| + final List<CodeSource> codeSources;
|
| + final String stepInfo;
|
| + int sourceMappingIndex;
|
| +
|
| + CodeLineAnnotation(
|
| + {this.annotationId,
|
| + this.annotationType,
|
| + this.codeLocations,
|
| + this.codeSources,
|
| + this.stepInfo,
|
| + this.sourceMappingIndex});
|
| +
|
| + Map toJson(JsonStrategy strategy) {
|
| + return {
|
| + 'annotationId': annotationId,
|
| + 'annotationType': annotationType.index,
|
| + 'codeLocations': codeLocations.map((l) => l.toJson(strategy)).toList(),
|
| + 'codeSources': codeSources.map((c) => c.toJson()).toList(),
|
| + 'stepInfo': stepInfo,
|
| + 'sourceMappingIndex': sourceMappingIndex,
|
| + };
|
| + }
|
| +
|
| + static fromJson(Map json, JsonStrategy strategy) {
|
| + return new CodeLineAnnotation(
|
| + annotationId: json['id'],
|
| + annotationType: AnnotationType.values[json['annotationType']],
|
| + codeLocations: json['codeLocations']
|
| + .map((j) => CodeLocation.fromJson(j, strategy))
|
| + .toList(),
|
| + codeSources: json['codeSources']
|
| + .map((j) => CodeSource.fromJson(j))
|
| + .toList(),
|
| + stepInfo: json['stepInfo'],
|
| + sourceMappingIndex: json['sourceMappingIndex']);
|
| }
|
| - return lines;
|
| }
|
|
|