62e5a482e4
Change-Id: Ic1cb02d587e100cefb5e6ee409693cfea1b0cfed Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352969 Reviewed-by: Nate Biggs <natebiggs@google.com>
767 lines
26 KiB
Dart
767 lines
26 KiB
Dart
// Copyright (c) 2016, 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 sourcemap.diff;
|
|
|
|
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,
|
|
MATCHING,
|
|
IDENTICAL,
|
|
}
|
|
|
|
/// Id for an output column.
|
|
class DiffColumn {
|
|
final String type;
|
|
final int? index;
|
|
|
|
const DiffColumn(this.type, [this.index]);
|
|
|
|
@override
|
|
int get hashCode => type.hashCode * 19 + index.hashCode * 23;
|
|
|
|
@override
|
|
bool operator ==(other) {
|
|
if (identical(this, other)) return true;
|
|
if (other is! DiffColumn) return false;
|
|
return type == other.type && index == other.index;
|
|
}
|
|
|
|
@override
|
|
String toString() => '$type${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);
|
|
|
|
@override
|
|
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);
|
|
|
|
@override
|
|
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;
|
|
final Map<DiffColumn, DiffColumnBlock> _columns =
|
|
<DiffColumn, DiffColumnBlock>{};
|
|
|
|
DiffBlock(this.kind);
|
|
|
|
void addColumnBlock(DiffColumn column, DiffColumnBlock block) {
|
|
_columns[column] = block;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _eq(a, b) => a == b;
|
|
|
|
/// Align the content of [list1] and [list2].
|
|
///
|
|
/// If provided, [range1] and [range2] aligned the subranges of [list1] and
|
|
/// [list2], otherwise the whole lists are aligned.
|
|
///
|
|
/// If provided, [match] determines the equality between members of [list1] and
|
|
/// [list2], otherwise `==` is used.
|
|
///
|
|
/// [handleSkew] is called when a subrange of one list is not found in the
|
|
/// other.
|
|
///
|
|
/// [handleMatched] is called when two indices match up.
|
|
///
|
|
/// [handleUnmatched] is called when two indices don't match up (none are found
|
|
/// in the other list).
|
|
void align(List list1, List list2,
|
|
{Interval? range1,
|
|
Interval? range2,
|
|
bool match(a, b) = _eq,
|
|
required void handleSkew(int listIndex, Interval range),
|
|
required void handleMatched(List<int> indices),
|
|
required void handleUnmatched(List<int> indices)}) {
|
|
range1 ??= Interval(0, list1.length);
|
|
range2 ??= Interval(0, list2.length);
|
|
|
|
Interval? findInOther(List thisLines, Interval thisRange, List otherLines,
|
|
Interval otherRange) {
|
|
for (int index = otherRange.from; index < otherRange.to; index++) {
|
|
if (match(thisLines[thisRange.from], otherLines[index])) {
|
|
int offset = 1;
|
|
while (thisRange.from + offset < thisRange.to &&
|
|
otherRange.from + offset < otherRange.to &&
|
|
match(thisLines[thisRange.from + offset],
|
|
otherLines[otherRange.from + offset])) {
|
|
offset++;
|
|
}
|
|
return Interval(index, index + offset);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
int start1 = range1.from;
|
|
int end1 = range1.to;
|
|
int start2 = range2.from;
|
|
int end2 = range2.to;
|
|
|
|
const int ALIGN1 = -1;
|
|
const int UNMATCHED = 0;
|
|
const int ALIGN2 = 1;
|
|
|
|
while (start1 < end1 && start2 < end2) {
|
|
if (match(list1[start1], list2[start2])) {
|
|
handleMatched([start1++, start2++]);
|
|
} else {
|
|
Interval subrange1 = Interval(start1, end1);
|
|
Interval subrange2 = Interval(start2, end2);
|
|
Interval? element2inList1 =
|
|
findInOther(list1, subrange1, list2, subrange2);
|
|
Interval? element1inList2 =
|
|
findInOther(list2, subrange2, list1, subrange1);
|
|
int choice = 0;
|
|
if (element2inList1 != null) {
|
|
if (element1inList2 != null) {
|
|
if (element1inList2.length > 1 && element2inList1.length > 1) {
|
|
choice =
|
|
element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1;
|
|
} else if (element2inList1.length > 1) {
|
|
choice = ALIGN2;
|
|
} else if (element1inList2.length > 1) {
|
|
choice = ALIGN1;
|
|
} else {
|
|
choice =
|
|
element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1;
|
|
}
|
|
} else {
|
|
choice = ALIGN2;
|
|
}
|
|
} else if (element1inList2 != null) {
|
|
choice = ALIGN1;
|
|
}
|
|
switch (choice) {
|
|
case ALIGN1:
|
|
handleSkew(0, Interval(start1, element1inList2!.from));
|
|
start1 = element1inList2.from;
|
|
break;
|
|
case ALIGN2:
|
|
handleSkew(1, Interval(start2, element2inList1!.from));
|
|
start2 = element2inList1.from;
|
|
break;
|
|
case UNMATCHED:
|
|
handleUnmatched([start1++, start2++]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (start1 < end1) {
|
|
handleSkew(0, Interval(start1, end1));
|
|
}
|
|
if (start2 < end2) {
|
|
handleSkew(1, Interval(start2, end2));
|
|
}
|
|
}
|
|
|
|
/// Create a list of blocks containing the diff of the two output [structures]
|
|
/// and the corresponding Dart code.
|
|
List<DiffBlock> createDiffBlocks(
|
|
List<OutputStructure> structures, SourceFileManager sourceFileManager) {
|
|
return DiffCreator(structures, sourceFileManager).computeBlocks();
|
|
}
|
|
|
|
class DiffCreator {
|
|
final List<OutputStructure> structures;
|
|
final SourceFileManager sourceFileManager;
|
|
|
|
List<List<CodeLine>> inputLines;
|
|
|
|
List<int> nextInputLine = [0, 0];
|
|
|
|
List<DiffBlock> blocks = <DiffBlock>[];
|
|
|
|
DiffCreator(List<OutputStructure> structures, this.sourceFileManager)
|
|
: this.structures = structures,
|
|
this.inputLines = structures.map((s) => s.lines).toList();
|
|
|
|
/// Compute [CodeSource]s defined by [entities].
|
|
Iterable<CodeSource> codeSourceFromEntities(Iterable<OutputEntity> entities) {
|
|
Set<CodeSource> sources = Set<CodeSource>();
|
|
for (OutputEntity entity in entities) {
|
|
if (entity.codeSource != null) {
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks that lines are added in sequence without gaps or duplicates.
|
|
void checkLineInvariant(int index, Interval range) {
|
|
int expectedLineNo = nextInputLine[index];
|
|
if (range.from != expectedLineNo) {
|
|
print('Expected line no $expectedLineNo, found ${range.from}');
|
|
if (range.from < expectedLineNo) {
|
|
print('Duplicate lines:');
|
|
int i = range.from;
|
|
while (i <= expectedLineNo) {
|
|
print(inputLines[index][i++].code);
|
|
}
|
|
} else {
|
|
print('Missing lines:');
|
|
int i = expectedLineNo;
|
|
while (i <= range.from) {
|
|
print(inputLines[index][i++].code);
|
|
}
|
|
}
|
|
}
|
|
nextInputLine[index] = range.to;
|
|
}
|
|
|
|
/// 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,
|
|
[Iterable<CodeSource> mainCodeSources = const <CodeSource>[]]) {
|
|
if (range.isEmpty) return;
|
|
|
|
Set<CodeSource> codeSources = Set<CodeSource>();
|
|
codeSources.addAll(mainCodeSources);
|
|
|
|
DiffBlock block = DiffBlock(DiffKind.UNMATCHED);
|
|
checkLineInvariant(index, range);
|
|
List<CodeLine> jsCodeLines =
|
|
inputLines[index].sublist(range.from, range.to);
|
|
Map<CodeLine, List<CodeLine>> dartCodeLines =
|
|
dartCodeLinesFromJsCodeLines(jsCodeLines);
|
|
block.addColumnBlock(new DiffColumn('js', index),
|
|
CodeLinesColumnBlock(jsCodeLines, dartCodeLines));
|
|
collectCodeSources(codeSources, dartCodeLines);
|
|
|
|
if (codeSources.isNotEmpty) {
|
|
block.addColumnBlock(const DiffColumn('dart'),
|
|
codeLinesFromCodeSources(mainCodeSources, codeSources));
|
|
}
|
|
blocks.add(block);
|
|
}
|
|
|
|
/// 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,
|
|
[Iterable<CodeSource> mainCodeSources = const <CodeSource>[]]) {
|
|
if (ranges.every((range) => range.isEmpty)) return;
|
|
|
|
Set<CodeSource> codeSources = Set<CodeSource>();
|
|
codeSources.addAll(mainCodeSources);
|
|
|
|
DiffBlock block = DiffBlock(kind);
|
|
for (int i = 0; i < ranges.length; i++) {
|
|
checkLineInvariant(i, ranges[i]);
|
|
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),
|
|
CodeLinesColumnBlock(jsCodeLines, dartCodeLines));
|
|
collectCodeSources(codeSources, dartCodeLines);
|
|
}
|
|
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;
|
|
List<Interval>? currentUnmatchedIntervals;
|
|
|
|
void flushMatching() {
|
|
if (currentMatchedIntervals != null) {
|
|
addLines(DiffKind.IDENTICAL, currentMatchedIntervals!);
|
|
}
|
|
currentMatchedIntervals = null;
|
|
}
|
|
|
|
void flushUnmatched() {
|
|
if (currentUnmatchedIntervals != null) {
|
|
addLines(DiffKind.UNMATCHED, currentUnmatchedIntervals!);
|
|
}
|
|
currentUnmatchedIntervals = null;
|
|
}
|
|
|
|
List<Interval> updateIntervals(List<Interval>? current, List<int> indices) {
|
|
if (current == null) {
|
|
return [
|
|
Interval(indices[0], indices[0] + 1),
|
|
Interval(indices[1], indices[1] + 1)
|
|
];
|
|
} else {
|
|
current[0] = Interval(current[0].from, indices[0] + 1);
|
|
current[1] = Interval(current[1].from, indices[1] + 1);
|
|
return current;
|
|
}
|
|
}
|
|
|
|
align(inputLines[0], inputLines[1],
|
|
range1: range1,
|
|
range2: range2,
|
|
match: match, handleSkew: (int listIndex, Interval range) {
|
|
flushMatching();
|
|
flushUnmatched();
|
|
handleSkew(listIndex, range);
|
|
}, handleMatched: (List<int> indices) {
|
|
flushUnmatched();
|
|
currentMatchedIntervals =
|
|
updateIntervals(currentMatchedIntervals, indices);
|
|
}, handleUnmatched: (List<int> indices) {
|
|
flushMatching();
|
|
currentUnmatchedIntervals =
|
|
updateIntervals(currentUnmatchedIntervals, indices);
|
|
});
|
|
|
|
flushMatching();
|
|
flushUnmatched();
|
|
}
|
|
|
|
/// Adds the top level blocks in [childRange] for structure [index].
|
|
void addBlock(int index, Interval childRange) {
|
|
addSkewedChildren(index, structures[index], childRange);
|
|
}
|
|
|
|
/// Adds the [entity] from structure [index]. If the [entity] supports child
|
|
/// entities, these are process individually. Otherwise the lines from
|
|
/// [entity] are added directly.
|
|
void addSkewedEntity(int index, OutputEntity entity) {
|
|
if (entity.canHaveChildren) {
|
|
handleSkew(index, entity.header);
|
|
addSkewedChildren(index, entity, Interval(0, entity.children.length));
|
|
handleSkew(index, entity.footer);
|
|
} else {
|
|
handleSkew(index, entity.interval, codeSourceFromEntities([entity]));
|
|
}
|
|
}
|
|
|
|
/// Adds the children of [parent] in [childRange] from structure [index].
|
|
void addSkewedChildren(int index, OutputEntity parent, Interval childRange) {
|
|
for (int i = childRange.from; i < childRange.to; i++) {
|
|
addSkewedEntity(index, parent.getChild(i));
|
|
}
|
|
}
|
|
|
|
/// Adds the members of the [classes] aligned.
|
|
void addMatchingContainers(List<OutputEntity> classes) {
|
|
addLines(DiffKind.MATCHING, classes.map((c) => c.header).toList());
|
|
align(classes[0].children, classes[1].children,
|
|
match: (a, b) => a.name == b.name,
|
|
handleSkew: (int listIndex, Interval childRange) {
|
|
addSkewedChildren(listIndex, classes[listIndex], childRange);
|
|
},
|
|
handleMatched: (List<int> indices) {
|
|
List<OutputEntity> entities = [
|
|
classes[0].getChild(indices[0]),
|
|
classes[1].getChild(indices[1])
|
|
];
|
|
if (entities.every((e) => e is Statics)) {
|
|
addMatchingContainers(entities);
|
|
} else {
|
|
addLines(
|
|
DiffKind.MATCHING,
|
|
entities.map((e) => e.interval).toList(),
|
|
codeSourceFromEntities(entities));
|
|
}
|
|
},
|
|
handleUnmatched: (List<int> indices) {
|
|
List<Interval> intervals = [
|
|
classes[0].getChild(indices[0]).interval,
|
|
classes[1].getChild(indices[1]).interval
|
|
];
|
|
addLines(DiffKind.UNMATCHED, intervals);
|
|
});
|
|
addLines(DiffKind.MATCHING, classes.map((c) => c.footer).toList());
|
|
}
|
|
|
|
/// Adds the library blocks in [indices] from the corresponding
|
|
/// [OutputStructure]s, aligning their content.
|
|
void addMatchingBlocks(List<int> indices) {
|
|
List<OutputEntity> blocks = [
|
|
structures[0].getChild(indices[0]),
|
|
structures[1].getChild(indices[1])
|
|
];
|
|
|
|
addLines(DiffKind.MATCHING, blocks.map((b) => b.header).toList());
|
|
align(blocks[0].children, blocks[1].children,
|
|
match: (a, b) => a.name == b.name,
|
|
handleSkew: (int listIndex, Interval childRange) {
|
|
addSkewedChildren(listIndex, blocks[listIndex], childRange);
|
|
},
|
|
handleMatched: (List<int> indices) {
|
|
List<OutputEntity> entities = [
|
|
blocks[0].getChild(indices[0]),
|
|
blocks[1].getChild(indices[1])
|
|
];
|
|
if (entities.every((e) => e is LibraryClass)) {
|
|
addMatchingContainers(entities);
|
|
} else {
|
|
addLines(
|
|
DiffKind.MATCHING,
|
|
entities.map((e) => e.interval).toList(),
|
|
codeSourceFromEntities(entities));
|
|
}
|
|
},
|
|
handleUnmatched: (List<int> indices) {
|
|
List<Interval> intervals = [
|
|
blocks[0].getChild(indices[0]).interval,
|
|
blocks[1].getChild(indices[1]).interval
|
|
];
|
|
addLines(DiffKind.UNMATCHED, intervals);
|
|
});
|
|
addLines(DiffKind.MATCHING, blocks.map((b) => b.footer).toList());
|
|
}
|
|
|
|
/// Adds the lines of the blocks in [indices] from the corresponding
|
|
/// [OutputStructure]s.
|
|
void addUnmatchedBlocks(List<int> indices) {
|
|
List<OutputEntity> blocks = [
|
|
structures[0].getChild(indices[0]),
|
|
structures[1].getChild(indices[1])
|
|
];
|
|
addLines(DiffKind.UNMATCHED, [blocks[0].interval, blocks[1].interval]);
|
|
}
|
|
|
|
/// Computes the diff blocks for [OutputStructure]s.
|
|
List<DiffBlock> computeBlocks() {
|
|
addRaw(structures[0].header, structures[1].header);
|
|
|
|
align(structures[0].children, structures[1].children,
|
|
match: (a, b) => a.name == b.name,
|
|
handleSkew: addBlock,
|
|
handleMatched: addMatchingBlocks,
|
|
handleUnmatched: addUnmatchedBlocks);
|
|
|
|
addRaw(structures[0].footer, structures[1].footer);
|
|
|
|
return blocks;
|
|
}
|
|
|
|
/// 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 = 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.getLocation(codeSource.begin!).line - 1;
|
|
int endLine = sourceFile.getLocation(codeSource.end!).line;
|
|
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;
|
|
late 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, () => 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;
|
|
late Interval currentLineInterval;
|
|
for (CodeLine dartCodeLine in annotatedDartCodeLines) {
|
|
if (currentCodeSource == null ||
|
|
!currentLineInterval.contains(dartCodeLine.lineNo)) {
|
|
currentCodeSource = null;
|
|
for (CodeSource codeSource in codeSources) {
|
|
Interval interval = Interval(
|
|
sourceFile.getLocation(codeSource.begin!).line - 1,
|
|
sourceFile.getLocation(codeSource.end!).line);
|
|
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 = 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.getLocation(location.offset).line - 1;
|
|
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';
|
|
}
|
|
|
|
enum AnnotationType {
|
|
withSourceInfo(ClassNames.withSourceInfo, true),
|
|
withoutSourceInfo(ClassNames.withoutSourceInfo, false),
|
|
additionalSourceInfo(ClassNames.additionalSourceInfo, true),
|
|
unusedSourceInfo(ClassNames.unusedSourceInfo, false),
|
|
;
|
|
|
|
final String className;
|
|
final bool isSourceMapped;
|
|
|
|
const AnnotationType(this.className, this.isSourceMapped);
|
|
}
|
|
|
|
class CodeLineAnnotation {
|
|
final int annotationId;
|
|
final AnnotationType annotationType;
|
|
final List<CodeLocation> codeLocations;
|
|
final List<CodeSource> codeSources;
|
|
final String? stepInfo;
|
|
int? sourceMappingIndex;
|
|
|
|
CodeLineAnnotation(
|
|
{required this.annotationId,
|
|
required this.annotationType,
|
|
required this.codeLocations,
|
|
required 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 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']);
|
|
}
|
|
}
|