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; |
} |