Chromium Code Reviews| 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..c8f1fc3184c808f615f9b41c3c6e12068d46a2e6 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.mainDart : 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 mainDart = 'main_dart'; |
|
Siggi Cherem (dart-lang)
2016/03/07 18:08:36
consider: mainDart => originalDart?
(mainDart sou
Johnni Winther
2016/03/08 09:15:18
Done.
I don't have other categories in mind, but
|
| + 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; |
| } |