OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library sourcemap.diff; |
| 6 |
| 7 import 'package:compiler/src/io/source_file.dart'; |
| 8 |
| 9 import 'html_parts.dart'; |
| 10 import 'output_structure.dart'; |
| 11 import 'sourcemap_helper.dart'; |
| 12 |
| 13 enum DiffKind { |
| 14 UNMATCHED, |
| 15 MATCHING, |
| 16 IDENTICAL, |
| 17 } |
| 18 |
| 19 /// A list of columns that should align in output. |
| 20 class DiffBlock { |
| 21 final DiffKind kind; |
| 22 List<List<HtmlPart>> columns = <List<HtmlPart>>[]; |
| 23 |
| 24 DiffBlock(this.kind); |
| 25 |
| 26 void addColumn(int index, List<HtmlPart> lines) { |
| 27 if (index >= columns.length) { |
| 28 columns.length = index + 1; |
| 29 } |
| 30 columns[index] = lines; |
| 31 } |
| 32 |
| 33 List<HtmlPart> getColumn(int index) { |
| 34 List<HtmlPart> lines; |
| 35 if (index < columns.length) { |
| 36 lines = columns[index]; |
| 37 } |
| 38 return lines != null ? lines : const <HtmlPart>[]; |
| 39 } |
| 40 } |
| 41 |
| 42 |
| 43 /// Align the content of [list1] and [list2]. |
| 44 /// |
| 45 /// If provided, [range1] and [range2] aligned the subranges of [list1] and |
| 46 /// [list2], otherwise the whole lists are aligned. |
| 47 /// |
| 48 /// If provided, [match] determines the equality between members of [list1] and |
| 49 /// [list2], otherwise `==` is used. |
| 50 /// |
| 51 /// [handleSkew] is called when a subrange of one list is not found in the |
| 52 /// other. |
| 53 /// |
| 54 /// [handleMatched] is called when two indices match up. |
| 55 /// |
| 56 /// [handleUnmatched] is called when two indices don't match up (none are found |
| 57 /// in the other list). |
| 58 void align(List list1, |
| 59 List list2, |
| 60 {Interval range1, |
| 61 Interval range2, |
| 62 bool match(a, b), |
| 63 void handleSkew(int listIndex, Interval range), |
| 64 void handleMatched(List<int> indices), |
| 65 void handleUnmatched(List<int> indices)}) { |
| 66 if (match == null) { |
| 67 match = (a, b) => a == b; |
| 68 } |
| 69 |
| 70 if (range1 == null) { |
| 71 range1 = new Interval(0, list1.length); |
| 72 } |
| 73 if (range2 == null) { |
| 74 range2 = new Interval(0, list2.length); |
| 75 } |
| 76 |
| 77 Interval findInOther( |
| 78 List thisLines, Interval thisRange, |
| 79 List otherLines, Interval otherRange) { |
| 80 for (int index = otherRange.from; index < otherRange.to; index++) { |
| 81 if (match(thisLines[thisRange.from], otherLines[index])) { |
| 82 int offset = 1; |
| 83 while (thisRange.from + offset < thisRange.to && |
| 84 otherRange.from + offset < otherRange.to && |
| 85 match(thisLines[thisRange.from + offset], |
| 86 otherLines[otherRange.from + offset])) { |
| 87 offset++; |
| 88 } |
| 89 return new Interval(index, index + offset); |
| 90 } |
| 91 } |
| 92 return null; |
| 93 } |
| 94 |
| 95 int start1 = range1.from; |
| 96 int end1 = range1.to; |
| 97 int start2 = range2.from; |
| 98 int end2 = range2.to; |
| 99 |
| 100 const int ALIGN1 = -1; |
| 101 const int UNMATCHED = 0; |
| 102 const int ALIGN2 = 1; |
| 103 |
| 104 while (start1 < end1 && start2 < end2) { |
| 105 if (match(list1[start1], list2[start2])) { |
| 106 handleMatched([start1++, start2++]); |
| 107 } else { |
| 108 Interval subrange1 = new Interval(start1, end1); |
| 109 Interval subrange2 = new Interval(start2, end2); |
| 110 Interval element2inList1 = |
| 111 findInOther(list1, subrange1, list2, subrange2); |
| 112 Interval element1inList2 = |
| 113 findInOther(list2, subrange2, list1, subrange1); |
| 114 int choice = 0; |
| 115 if (element2inList1 != null) { |
| 116 if (element1inList2 != null) { |
| 117 if (element1inList2.length > 1 && element2inList1.length > 1) { |
| 118 choice = |
| 119 element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1; |
| 120 } else if (element2inList1.length > 1) { |
| 121 choice = ALIGN2; |
| 122 } else if (element1inList2.length > 1) { |
| 123 choice = ALIGN1; |
| 124 } else { |
| 125 choice = |
| 126 element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1; |
| 127 } |
| 128 } else { |
| 129 choice = ALIGN2; |
| 130 } |
| 131 } else if (element1inList2 != null) { |
| 132 choice = ALIGN1; |
| 133 } |
| 134 switch (choice) { |
| 135 case ALIGN1: |
| 136 handleSkew(0, new Interval(start1, element1inList2.from)); |
| 137 start1 = element1inList2.from; |
| 138 break; |
| 139 case ALIGN2: |
| 140 handleSkew(1, new Interval(start2, element2inList1.from)); |
| 141 start2 = element2inList1.from; |
| 142 break; |
| 143 case UNMATCHED: |
| 144 handleUnmatched([start1++, start2++]); |
| 145 break; |
| 146 } |
| 147 } |
| 148 } |
| 149 if (start1 < end1) { |
| 150 handleSkew(0, new Interval(start1, end1)); |
| 151 } |
| 152 if (start2 < end2) { |
| 153 handleSkew(1, new Interval(start2, end2)); |
| 154 } |
| 155 } |
| 156 |
| 157 /// Create a list of blocks containing the diff of the two output [structures] |
| 158 /// and the corresponding Dart code. |
| 159 List<DiffBlock> createDiffBlocks( |
| 160 List<OutputStructure> structures, |
| 161 SourceFileManager sourceFileManager) { |
| 162 return new DiffCreator(structures, sourceFileManager).computeBlocks(); |
| 163 } |
| 164 |
| 165 class DiffCreator { |
| 166 final List<OutputStructure> structures; |
| 167 final SourceFileManager sourceFileManager; |
| 168 |
| 169 List<List<CodeLine>> inputLines; |
| 170 |
| 171 List<int> nextInputLine = [0, 0]; |
| 172 |
| 173 List<DiffBlock> blocks = <DiffBlock>[]; |
| 174 |
| 175 DiffCreator(List<OutputStructure> structures, this.sourceFileManager) |
| 176 : this.structures = structures, |
| 177 this.inputLines = structures.map((s) => s.lines).toList(); |
| 178 |
| 179 CodeSource codeSourceFromEntities(Iterable<OutputEntity> entities) { |
| 180 for (OutputEntity entity in entities) { |
| 181 if (entity.codeSource != null) { |
| 182 return entity.codeSource; |
| 183 } |
| 184 } |
| 185 return null; |
| 186 } |
| 187 |
| 188 /// Checks that lines are added in sequence without gaps or duplicates. |
| 189 void checkLineInvariant(int index, Interval range) { |
| 190 int expectedLineNo = nextInputLine[index]; |
| 191 if (range.from != expectedLineNo) { |
| 192 print('Expected line no $expectedLineNo, found ${range.from}'); |
| 193 if (range.from < expectedLineNo) { |
| 194 print('Duplicate lines:'); |
| 195 int i = range.from; |
| 196 while (i <= expectedLineNo) { |
| 197 print(inputLines[index][i++].code); |
| 198 } |
| 199 } else { |
| 200 print('Missing lines:'); |
| 201 int i = expectedLineNo; |
| 202 while (i <= range.from) { |
| 203 print(inputLines[index][i++].code); |
| 204 } |
| 205 } |
| 206 } |
| 207 nextInputLine[index] = range.to; |
| 208 } |
| 209 |
| 210 /// Creates a block containing the code lines in [range] from input number |
| 211 /// [index]. If [codeSource] is provided, the block will contain a |
| 212 /// corresponding Dart code column. |
| 213 void handleSkew(int index, Interval range, [CodeSource codeSource]) { |
| 214 DiffBlock block = new DiffBlock(DiffKind.UNMATCHED); |
| 215 checkLineInvariant(index, range); |
| 216 block.addColumn(index, inputLines[index].sublist(range.from, range.to)); |
| 217 if (codeSource != null) { |
| 218 block.addColumn(2, |
| 219 codeLinesFromCodeSource(sourceFileManager, codeSource)); |
| 220 } |
| 221 blocks.add(block); |
| 222 } |
| 223 |
| 224 /// Create a block containing the code lines in [ranges] from the |
| 225 /// corresponding JavaScript inputs. If [codeSource] is provided, the block |
| 226 /// will contain a corresponding Dart code column. |
| 227 void addLines(DiffKind kind, List<Interval> ranges, [CodeSource codeSource]) { |
| 228 DiffBlock block = new DiffBlock(kind); |
| 229 for (int i = 0; i < ranges.length; i++) { |
| 230 checkLineInvariant(i, ranges[i]); |
| 231 block.addColumn(i, inputLines[i].sublist(ranges[i].from, ranges[i].to)); |
| 232 } |
| 233 if (codeSource != null) { |
| 234 block.addColumn(2, |
| 235 codeLinesFromCodeSource(sourceFileManager, codeSource)); |
| 236 } |
| 237 blocks.add(block); |
| 238 } |
| 239 |
| 240 /// Merge the code lines in [range1] and [range2] of the corresponding input. |
| 241 void addRaw(Interval range1, Interval range2) { |
| 242 match(a, b) => a.code == b.code; |
| 243 |
| 244 List<Interval> currentMatchedIntervals; |
| 245 List<Interval> currentUnmatchedIntervals; |
| 246 |
| 247 void flushMatching() { |
| 248 if (currentMatchedIntervals != null) { |
| 249 addLines(DiffKind.IDENTICAL, currentMatchedIntervals); |
| 250 } |
| 251 currentMatchedIntervals = null; |
| 252 } |
| 253 |
| 254 void flushUnmatched() { |
| 255 if (currentUnmatchedIntervals != null) { |
| 256 addLines(DiffKind.UNMATCHED, currentUnmatchedIntervals); |
| 257 } |
| 258 currentUnmatchedIntervals = null; |
| 259 } |
| 260 |
| 261 List<Interval> updateIntervals(List<Interval> current, List<int> indices) { |
| 262 if (current == null) { |
| 263 return [ |
| 264 new Interval(indices[0], indices[0] + 1), |
| 265 new Interval(indices[1], indices[1] + 1)]; |
| 266 } else { |
| 267 current[0] = |
| 268 new Interval(current[0].from, indices[0] + 1); |
| 269 current[1] = |
| 270 new Interval(current[1].from, indices[1] + 1); |
| 271 return current; |
| 272 } |
| 273 } |
| 274 |
| 275 align( |
| 276 inputLines[0], |
| 277 inputLines[1], |
| 278 range1: range1, |
| 279 range2: range2, |
| 280 match: match, |
| 281 handleSkew: (int listIndex, Interval range) { |
| 282 flushMatching(); |
| 283 flushUnmatched(); |
| 284 handleSkew(listIndex, range); |
| 285 }, |
| 286 handleMatched: (List<int> indices) { |
| 287 flushUnmatched(); |
| 288 currentMatchedIntervals = |
| 289 updateIntervals(currentMatchedIntervals, indices); |
| 290 }, |
| 291 handleUnmatched: (List<int> indices) { |
| 292 flushMatching(); |
| 293 currentUnmatchedIntervals = |
| 294 updateIntervals(currentUnmatchedIntervals, indices); |
| 295 }); |
| 296 |
| 297 flushMatching(); |
| 298 flushUnmatched(); |
| 299 } |
| 300 |
| 301 /// Adds the top level blocks in [childRange] for structure [index]. |
| 302 void addBlock(int index, Interval childRange) { |
| 303 addSkewedChildren(index, structures[index], childRange); |
| 304 } |
| 305 |
| 306 /// Adds the [entity] from structure [index]. If the [entity] supports child |
| 307 /// entities, these are process individually. Otherwise the lines from |
| 308 /// [entity] are added directly. |
| 309 void addSkewedEntity(int index, OutputEntity entity) { |
| 310 if (entity.canHaveChildren) { |
| 311 handleSkew(index, entity.header); |
| 312 addSkewedChildren( |
| 313 index, entity, new Interval(0, entity.children.length)); |
| 314 handleSkew(index, entity.footer); |
| 315 } else { |
| 316 handleSkew(index, entity.interval, codeSourceFromEntities([entity])); |
| 317 } |
| 318 } |
| 319 |
| 320 /// Adds the children of [parent] in [childRange] from structure [index]. |
| 321 void addSkewedChildren(int index, OutputEntity parent, Interval childRange) { |
| 322 for (int i = childRange.from; i < childRange.to; i++) { |
| 323 addSkewedEntity(index, parent.getChild(i)); |
| 324 } |
| 325 } |
| 326 |
| 327 /// Adds the members of the [classes] aligned. |
| 328 void addMatchingContainers(List<OutputEntity> classes) { |
| 329 addLines(DiffKind.MATCHING, classes.map((c) => c.header).toList()); |
| 330 align(classes[0].children, classes[1].children, |
| 331 match: (a, b) => a.name == b.name, |
| 332 handleSkew: (int listIndex, Interval childRange) { |
| 333 addSkewedChildren(listIndex, classes[listIndex], childRange); |
| 334 }, |
| 335 handleMatched: (List<int> indices) { |
| 336 List<BasicEntity> entities = [ |
| 337 classes[0].getChild(indices[0]), |
| 338 classes[1].getChild(indices[1])]; |
| 339 if (entities.every((e) => e is Statics)) { |
| 340 addMatchingContainers(entities); |
| 341 } else { |
| 342 addLines(DiffKind.MATCHING, |
| 343 entities.map((e) => e.interval).toList(), |
| 344 codeSourceFromEntities(entities)); |
| 345 } |
| 346 }, |
| 347 handleUnmatched: (List<int> indices) { |
| 348 List<Interval> intervals = [ |
| 349 classes[0].getChild(indices[0]).interval, |
| 350 classes[1].getChild(indices[1]).interval]; |
| 351 addLines(DiffKind.UNMATCHED, intervals); |
| 352 }); |
| 353 addLines(DiffKind.MATCHING, classes.map((c) => c.footer).toList()); |
| 354 } |
| 355 |
| 356 /// Adds the library blocks in [indices] from the corresponding |
| 357 /// [OutputStructure]s, aligning their content. |
| 358 void addMatchingBlocks(List<int> indices) { |
| 359 List<LibraryBlock> blocks = [ |
| 360 structures[0].getChild(indices[0]), |
| 361 structures[1].getChild(indices[1])]; |
| 362 |
| 363 addLines(DiffKind.MATCHING, blocks.map((b) => b.header).toList()); |
| 364 align(blocks[0].children, blocks[1].children, |
| 365 match: (a, b) => a.name == b.name, |
| 366 handleSkew: (int listIndex, Interval childRange) { |
| 367 addSkewedChildren( |
| 368 listIndex, blocks[listIndex], childRange); |
| 369 }, |
| 370 handleMatched: (List<int> indices) { |
| 371 List<BasicEntity> entities = [ |
| 372 blocks[0].getChild(indices[0]), |
| 373 blocks[1].getChild(indices[1])]; |
| 374 if (entities.every((e) => e is LibraryClass)) { |
| 375 addMatchingContainers(entities); |
| 376 } else { |
| 377 addLines( |
| 378 DiffKind.MATCHING, |
| 379 entities.map((e) => e.interval).toList(), |
| 380 codeSourceFromEntities(entities)); |
| 381 } |
| 382 }, |
| 383 handleUnmatched: (List<int> indices) { |
| 384 List<Interval> intervals = [ |
| 385 blocks[0].getChild(indices[0]).interval, |
| 386 blocks[1].getChild(indices[1]).interval]; |
| 387 addLines(DiffKind.UNMATCHED, intervals); |
| 388 }); |
| 389 addLines(DiffKind.MATCHING, blocks.map((b) => b.footer).toList()); |
| 390 } |
| 391 |
| 392 /// Adds the lines of the blocks in [indices] from the corresponding |
| 393 /// [OutputStructure]s. |
| 394 void addUnmatchedBlocks(List<int> indices) { |
| 395 List<LibraryBlock> blocks = [ |
| 396 structures[0].getChild(indices[0]), |
| 397 structures[1].getChild(indices[1])]; |
| 398 addLines(DiffKind.UNMATCHED, [blocks[0].interval, blocks[1].interval]); |
| 399 } |
| 400 |
| 401 /// Computes the diff blocks for [OutputStructure]s. |
| 402 List<DiffBlock> computeBlocks() { |
| 403 addRaw(structures[0].header, structures[1].header); |
| 404 |
| 405 align(structures[0].children, |
| 406 structures[1].children, |
| 407 match: (a, b) => a.name == b.name, |
| 408 handleSkew: addBlock, |
| 409 handleMatched: addMatchingBlocks, |
| 410 handleUnmatched: addUnmatchedBlocks); |
| 411 |
| 412 addRaw(structures[0].footer, structures[1].footer); |
| 413 |
| 414 return blocks; |
| 415 } |
| 416 } |
| 417 |
| 418 /// Creates html lines for code lines in [codeSource]. [sourceFileManager] is |
| 419 /// used to read that text from the source URIs. |
| 420 List<HtmlPart> codeLinesFromCodeSource( |
| 421 SourceFileManager sourceFileManager, |
| 422 CodeSource codeSource) { |
| 423 List<HtmlPart> lines = <HtmlPart>[]; |
| 424 SourceFile sourceFile = sourceFileManager.getSourceFile(codeSource.uri); |
| 425 String elementName = codeSource.name; |
| 426 HtmlLine line = new HtmlLine(); |
| 427 line.htmlParts.add(new ConstHtmlPart('<span class="comment">')); |
| 428 line.htmlParts.add(new HtmlText( |
| 429 '${elementName}: ${sourceFile.filename}')); |
| 430 line.htmlParts.add(new ConstHtmlPart('</span>')); |
| 431 lines.add(line); |
| 432 if (codeSource.begin != null) { |
| 433 int startLine = sourceFile.getLine(codeSource.begin); |
| 434 int endLine = sourceFile.getLine(codeSource.end); |
| 435 for (int lineNo = startLine; lineNo <= endLine; lineNo++) { |
| 436 String text = sourceFile.getLineText(lineNo); |
| 437 CodeLine codeLine = new CodeLine(lineNo, sourceFile.getOffset(lineNo, 0)); |
| 438 codeLine.codeBuffer.write(text); |
| 439 codeLine.htmlParts.add(new HtmlText(text)); |
| 440 lines.add(codeLine); |
| 441 } |
| 442 } |
| 443 return lines; |
| 444 } |
OLD | NEW |