| Index: tests/compiler/dart2js/sourcemaps/diff_view.dart
 | 
| diff --git a/tests/compiler/dart2js/sourcemaps/diff_view.dart b/tests/compiler/dart2js/sourcemaps/diff_view.dart
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..fc9eb6197cf3f0dc9f9ec6d388536cdec4bd765d
 | 
| --- /dev/null
 | 
| +++ b/tests/compiler/dart2js/sourcemaps/diff_view.dart
 | 
| @@ -0,0 +1,871 @@
 | 
| +// Copyright (c) 2015, 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_view;
 | 
| +
 | 
| +import 'dart:async';
 | 
| +import 'dart:io';
 | 
| +import 'package:compiler/src/commandline_options.dart';
 | 
| +import 'package:compiler/src/diagnostics/invariant.dart';
 | 
| +import 'package:compiler/src/io/position_information.dart';
 | 
| +import 'package:compiler/src/js/js.dart' as js;
 | 
| +import 'sourcemap_helper.dart';
 | 
| +import 'sourcemap_html_helper.dart';
 | 
| +import 'trace_graph.dart';
 | 
| +import 'js_tracer.dart';
 | 
| +
 | 
| +const String WITH_SOURCE_INFO_STYLE = 'background-color:#FF8080;';
 | 
| +const String WITHOUT_SOURCE_INFO_STYLE = 'border: solid 1px #FF8080;';
 | 
| +const String ADDITIONAL_SOURCE_INFO_STYLE = 'border: solid 1px #8080FF;';
 | 
| +
 | 
| +main(List<String> args) async {
 | 
| +  DEBUG_MODE = true;
 | 
| +  String out = 'out.js.diff_view.html';
 | 
| +  String filename;
 | 
| +  List<String> currentOptions = [];
 | 
| +  List<List<String>> options = [currentOptions];
 | 
| +  int argGroup = 0;
 | 
| +  for (String arg in args) {
 | 
| +    if (arg == '--') {
 | 
| +      currentOptions = [];
 | 
| +      options.add(currentOptions);
 | 
| +      argGroup++;
 | 
| +    } else if (arg.startsWith('-o')) {
 | 
| +      out = arg.substring('-o'.length);
 | 
| +    } else if (arg.startsWith('--out=')) {
 | 
| +      out = arg.substring('--out='.length);
 | 
| +    } else if (arg.startsWith('-')) {
 | 
| +      currentOptions.add(arg);
 | 
| +    } else {
 | 
| +      filename = arg;
 | 
| +    }
 | 
| +  }
 | 
| +  List<String> commonArguments = options[0];
 | 
| +  List<String> options1;
 | 
| +  List<String> options2;
 | 
| +  if (options.length == 1) {
 | 
| +    // Use default options; comparing SSA and CPS output using the new
 | 
| +    // source information strategy.
 | 
| +    options1 = [USE_NEW_SOURCE_INFO]..addAll(commonArguments);
 | 
| +    options2 = [USE_NEW_SOURCE_INFO, Flags.useCpsIr]..addAll(commonArguments);
 | 
| +  } else if (options.length == 2) {
 | 
| +    // Use alternative options for the second output column.
 | 
| +    options1 = commonArguments;
 | 
| +    options2 = options[1]..addAll(commonArguments);
 | 
| +  } else {
 | 
| +    // Use specific options for both output columns.
 | 
| +    options1 = options[1]..addAll(commonArguments);
 | 
| +    options2 = options[2]..addAll(commonArguments);
 | 
| +  }
 | 
| +
 | 
| +  print('Compiling ${options1.join(' ')} $filename');
 | 
| +  CodeLinesResult result1 = await computeCodeLines(options1, filename);
 | 
| +  print('Compiling ${options2.join(' ')} $filename');
 | 
| +  CodeLinesResult result2 = await computeCodeLines(options2, filename);
 | 
| +
 | 
| +  StringBuffer sb = new StringBuffer();
 | 
| +  sb.write('''
 | 
| +<html>
 | 
| +<head>
 | 
| +<title>Diff for $filename</title>
 | 
| +<style>
 | 
| +.lineNumber {
 | 
| +  font-size: smaller;
 | 
| +  color: #888;
 | 
| +}
 | 
| +.header {
 | 
| +  position: fixed;
 | 
| +  width: 50%;
 | 
| +  background-color: #400000;
 | 
| +  color: #FFFFFF;
 | 
| +  height: 20px;
 | 
| +  top: 0px;
 | 
| +  z-index: 1000;
 | 
| +}
 | 
| +.cell {
 | 
| +  max-width:500px;
 | 
| +  overflow-x:auto;
 | 
| +  vertical-align:top;
 | 
| +}
 | 
| +.corresponding1 {
 | 
| +  background-color: #FFFFE0;
 | 
| +}
 | 
| +.corresponding2 {
 | 
| +  background-color: #EFEFD0;
 | 
| +}
 | 
| +.identical1 {
 | 
| +  background-color: #E0F0E0;
 | 
| +}
 | 
| +.identical2 {
 | 
| +  background-color: #C0E0C0;
 | 
| +}
 | 
| +</style>
 | 
| +</head>
 | 
| +<body>''');
 | 
| +
 | 
| +  sb.write('''
 | 
| +<div class="header" style="left: 0px;">[${options1.join(',')}]</div>
 | 
| +<div class="header" style="right: 0px;">[${options2.join(',')}]</div>
 | 
| +<div style="position:absolute;top:22px;width:100%;height:18px;">
 | 
| +  <span class="identical1">   </span> 
 | 
| +  <span class="identical2">   </span>
 | 
| +  identical blocks
 | 
| +  <span class="corresponding1">   </span>
 | 
| +  <span class="corresponding2">   </span> 
 | 
| +  corresponding blocks
 | 
| +  <span style="$WITH_SOURCE_INFO_STYLE">   </span>
 | 
| +  offset with source information
 | 
| +  <span style="$WITHOUT_SOURCE_INFO_STYLE">   </span>
 | 
| +  offset without source information
 | 
| +  <span style="$ADDITIONAL_SOURCE_INFO_STYLE">   </span>
 | 
| +  offset with unneeded source information
 | 
| +</div>
 | 
| +<table style="position:absolute;top:40px;width:100%;"><tr>
 | 
| +''');
 | 
| +
 | 
| +  void addCell(String content) {
 | 
| +    sb.write('''
 | 
| +<td class="cell"><pre>
 | 
| +''');
 | 
| +    sb.write(content);
 | 
| +    sb.write('''
 | 
| +</pre></td>
 | 
| +''');
 | 
| +  }
 | 
| +
 | 
| +  List<OutputStructure> structures = [
 | 
| +      OutputStructure.parse(result1.codeLines),
 | 
| +      OutputStructure.parse(result2.codeLines)];
 | 
| +  List<List<CodeLine>> inputLines = [result1.codeLines, result2.codeLines];
 | 
| +  List<List<HtmlPart>> outputLines = [<HtmlPart>[], <HtmlPart>[]];
 | 
| +
 | 
| +  /// Marker to alternate output colors.
 | 
| +  bool alternating = false;
 | 
| +
 | 
| +  /// Enable 'corresponding' background colors for [f].
 | 
| +  void withMatching(f()) {
 | 
| +    HtmlPart start = new ConstHtmlPart(
 | 
| +        '<div class="corresponding${alternating ? '1' : '2'}">');
 | 
| +    HtmlPart end = new ConstHtmlPart('</div>');
 | 
| +    alternating = !alternating;
 | 
| +    outputLines[0].add(start);
 | 
| +    outputLines[1].add(start);
 | 
| +    f();
 | 
| +    outputLines[0].add(end);
 | 
| +    outputLines[1].add(end);
 | 
| +  }
 | 
| +
 | 
| +  /// Enable 'identical' background colors for [f].
 | 
| +  void withIdentical(f()) {
 | 
| +    HtmlPart start = new ConstHtmlPart(
 | 
| +        '<div class="identical${alternating ? '1' : '2'}">');
 | 
| +    HtmlPart end = new ConstHtmlPart('</div>');
 | 
| +    alternating = !alternating;
 | 
| +    outputLines[0].add(start);
 | 
| +    outputLines[1].add(start);
 | 
| +    f();
 | 
| +    outputLines[0].add(end);
 | 
| +    outputLines[1].add(end);
 | 
| +  }
 | 
| +
 | 
| +  /// Output code lines in [range] from input number [index], padding the other
 | 
| +  /// column with empty lines.
 | 
| +  void handleSkew(int index, Interval range) {
 | 
| +    int from = range.from;
 | 
| +    while (from < range.to) {
 | 
| +      outputLines[1 - index].add(const ConstHtmlPart('\n'));
 | 
| +      outputLines[index].add(
 | 
| +          new CodeLineHtmlPart(inputLines[index][from++]));
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  /// Output code lines of the [indices] from the corresponding inputs.
 | 
| +  void addBoth(List<int> indices) {
 | 
| +    outputLines[0].add(new CodeLineHtmlPart(inputLines[0][indices[0]]));
 | 
| +    outputLines[1].add(new CodeLineHtmlPart(inputLines[1][indices[1]]));
 | 
| +  }
 | 
| +
 | 
| +  /// Output code lines of the [ranges] from the corresponding inputs.
 | 
| +  void addBothLines(List<Interval> ranges) {
 | 
| +    Interval range1 = ranges[0];
 | 
| +    Interval range2 = ranges[1];
 | 
| +    int offset = 0;
 | 
| +    while (range1.from + offset < range1.to &&
 | 
| +           range2.from + offset < range2.to) {
 | 
| +      addBoth([range1.from + offset, range2.from + offset]);
 | 
| +      offset++;
 | 
| +    }
 | 
| +    if (range1.from + offset < range1.to) {
 | 
| +      handleSkew(0, new Interval(range1.from + offset, range1.to));
 | 
| +    }
 | 
| +    if (range2.from + offset < range2.to) {
 | 
| +      handleSkew(1, new Interval(range2.from + offset, range2.to));
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  /// Merge the code lines in [range1] and [range2] of the corresponding input.
 | 
| +  void addRaw(Interval range1, Interval range2) {
 | 
| +    match(a, b) => a.code == b.code;
 | 
| +
 | 
| +    List<Interval> currentMatchedIntervals;
 | 
| +
 | 
| +    void flushMatching() {
 | 
| +      if (currentMatchedIntervals != null) {
 | 
| +        withIdentical(() {
 | 
| +          addBothLines(currentMatchedIntervals);
 | 
| +        });
 | 
| +      }
 | 
| +      currentMatchedIntervals = null;
 | 
| +    }
 | 
| +
 | 
| +    align(
 | 
| +        inputLines[0],
 | 
| +        inputLines[1],
 | 
| +        range1: range1,
 | 
| +        range2: range2,
 | 
| +        match: match,
 | 
| +        handleSkew: (int listIndex, Interval range) {
 | 
| +          flushMatching();
 | 
| +          handleSkew(listIndex, range);
 | 
| +        },
 | 
| +        handleMatched: (List<int> indices) {
 | 
| +          if (currentMatchedIntervals == null) {
 | 
| +            currentMatchedIntervals = [
 | 
| +              new Interval(indices[0], indices[0] + 1),
 | 
| +              new Interval(indices[1], indices[1] + 1)];
 | 
| +          } else {
 | 
| +            currentMatchedIntervals[0] =
 | 
| +                new Interval(currentMatchedIntervals[0].from, indices[0] + 1);
 | 
| +            currentMatchedIntervals[1] =
 | 
| +                new Interval(currentMatchedIntervals[1].from, indices[1] + 1);
 | 
| +          }
 | 
| +        },
 | 
| +        handleUnmatched: (List<int> indices) {
 | 
| +          flushMatching();
 | 
| +          addBoth(indices);
 | 
| +        });
 | 
| +
 | 
| +    flushMatching();
 | 
| +  }
 | 
| +
 | 
| +  /// Output the lines of the library blocks in [childRange] in
 | 
| +  /// `structures[index]`, padding the other column with empty lines.
 | 
| +  void addBlock(int index, Interval childRange) {
 | 
| +    handleSkew(index, structures[index].getChildInterval(childRange));
 | 
| +  }
 | 
| +
 | 
| +  /// Output the members of the [classes] aligned.
 | 
| +  void addMatchingClasses(List<LibraryClass> classes) {
 | 
| +    withMatching(() {
 | 
| +      addBothLines(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) {
 | 
| +          handleSkew(listIndex,
 | 
| +              classes[listIndex].getChildInterval(childRange));
 | 
| +        },
 | 
| +        handleMatched: (List<int> indices) {
 | 
| +          List<Interval> intervals =  [
 | 
| +              classes[0].getChild(indices[0]).interval,
 | 
| +              classes[1].getChild(indices[1]).interval];
 | 
| +          withMatching(() {
 | 
| +            addBothLines(intervals);
 | 
| +          });
 | 
| +        },
 | 
| +        handleUnmatched: (List<int> indices) {
 | 
| +          List<Interval> intervals =  [
 | 
| +              classes[0].getChild(indices[0]).interval,
 | 
| +              classes[1].getChild(indices[1]).interval];
 | 
| +          addBothLines(intervals);
 | 
| +        });
 | 
| +    withMatching(() {
 | 
| +      addBothLines(classes.map((c) => c.footer).toList());
 | 
| +    });
 | 
| +  }
 | 
| +
 | 
| +  /// Output the library blocks in [indices] from the corresponding
 | 
| +  /// [OutputStructure]s, aligning their content.
 | 
| +  void addMatchingBlocks(List<int> indices) {
 | 
| +    List<LibraryBlock> blocks = [
 | 
| +      structures[0].getChild(indices[0]),
 | 
| +      structures[1].getChild(indices[1])];
 | 
| +
 | 
| +    withMatching(() {
 | 
| +      addBothLines(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) {
 | 
| +          handleSkew(listIndex, blocks[listIndex].getChildInterval(childRange));
 | 
| +        },
 | 
| +        handleMatched: (List<int> indices) {
 | 
| +          List<BasicEntity> entities =  [
 | 
| +              blocks[0].getChild(indices[0]),
 | 
| +              blocks[1].getChild(indices[1])];
 | 
| +          if (entities.every((e) => e is LibraryClass)) {
 | 
| +            addMatchingClasses(entities);
 | 
| +          } else {
 | 
| +            withMatching(() {
 | 
| +              addBothLines(entities.map((e) => e.interval).toList());
 | 
| +            });
 | 
| +          }
 | 
| +        },
 | 
| +        handleUnmatched: (List<int> indices) {
 | 
| +          List<Interval> intervals =  [
 | 
| +              blocks[0].getChild(indices[0]).interval,
 | 
| +              blocks[1].getChild(indices[1]).interval];
 | 
| +          addBothLines(intervals);
 | 
| +        });
 | 
| +    withMatching(() {
 | 
| +      addBothLines(blocks.map((b) => b.footer).toList());
 | 
| +    });
 | 
| +  }
 | 
| +
 | 
| +  /// Output the lines of the blocks in [indices] from the corresponding
 | 
| +  /// [OutputStructure]s.
 | 
| +  void addUnmatchedBlocks(List<int> indices) {
 | 
| +    List<LibraryBlock> blocks = [
 | 
| +       structures[0].getChild(indices[0]),
 | 
| +       structures[1].getChild(indices[1])];
 | 
| +    addBothLines([blocks[0].interval, blocks[1].interval]);
 | 
| +  }
 | 
| +
 | 
| +
 | 
| +  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);
 | 
| +
 | 
| +  addCell(htmlPartsToString(outputLines[0], inputLines[0]));
 | 
| +  addCell(htmlPartsToString(outputLines[1], inputLines[1]));
 | 
| +
 | 
| +  sb.write('''</tr><tr>''');
 | 
| +  addCell(result1.coverage.getCoverageReport());
 | 
| +  addCell(result2.coverage.getCoverageReport());
 | 
| +
 | 
| +  sb.write('''
 | 
| +</tr></table>
 | 
| +</body>
 | 
| +</html>
 | 
| +''');
 | 
| +
 | 
| +  new File(out).writeAsStringSync(sb.toString());
 | 
| +  print('Diff generated in $out');
 | 
| +}
 | 
| +
 | 
| +/// 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),
 | 
| +            void handleSkew(int listIndex, Interval range),
 | 
| +            void handleMatched(List<int> indices),
 | 
| +            void handleUnmatched(List<int> indices)}) {
 | 
| +  if (match == null) {
 | 
| +    match = (a, b) => a == b;
 | 
| +  }
 | 
| +
 | 
| +  if (range1 == null) {
 | 
| +    range1 = new Interval(0, list1.length);
 | 
| +  }
 | 
| +  if (range2 == null) {
 | 
| +    range2 = new 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 new 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 = new Interval(start1, end1);
 | 
| +      Interval subrange2 = new 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, new Interval(start1, element1inList2.from));
 | 
| +          start1 = element1inList2.from;
 | 
| +          break;
 | 
| +        case ALIGN2:
 | 
| +          handleSkew(1, new Interval(start2, element2inList1.from));
 | 
| +          start2 = element2inList1.from;
 | 
| +          break;
 | 
| +        case UNMATCHED:
 | 
| +          handleUnmatched([start1++, start2++]);
 | 
| +          break;
 | 
| +      }
 | 
| +    }
 | 
| +  }
 | 
| +  if (start1 < end1) {
 | 
| +    handleSkew(0, new Interval(start1, end1));
 | 
| +  }
 | 
| +  if (start2 < end2) {
 | 
| +    handleSkew(1, new Interval(start2, end2));
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +// Constants used to identify the subsection of the JavaScript output. These
 | 
| +// are specifically for the unminified full_emitter output.
 | 
| +const String HEAD = '  var dart = [';
 | 
| +const String TAIL = '  }], ';
 | 
| +const String END = '  setupProgram(dart';
 | 
| +
 | 
| +final RegExp TOP_LEVEL_VALUE = new RegExp(r'^    (".+?"):');
 | 
| +final RegExp TOP_LEVEL_FUNCTION =
 | 
| +    new RegExp(r'^    ([a-zA-Z0-9_$]+): \[?function');
 | 
| +final RegExp TOP_LEVEL_CLASS = new RegExp(r'^    ([a-zA-Z0-9_$]+): \[?\{');
 | 
| +
 | 
| +final RegExp MEMBER_VALUE = new RegExp(r'^      (".+?"):');
 | 
| +final RegExp MEMBER_FUNCTION =
 | 
| +    new RegExp(r'^      ([a-zA-Z0-9_$]+): \[?function');
 | 
| +final RegExp MEMBER_OBJECT = new RegExp(r'^      ([a-zA-Z0-9_$]+): \[?\{');
 | 
| +
 | 
| +/// Subrange of the JavaScript output.
 | 
| +abstract class OutputEntity {
 | 
| +  Interval get interval;
 | 
| +  Interval get header;
 | 
| +  Interval get footer;
 | 
| +
 | 
| +  List<OutputEntity> get children;
 | 
| +
 | 
| +  Interval getChildInterval(Interval childIndex) {
 | 
| +    return new Interval(
 | 
| +        children[childIndex.from].interval.from,
 | 
| +        children[childIndex.to - 1].interval.to);
 | 
| +
 | 
| +  }
 | 
| +
 | 
| +  OutputEntity getChild(int index) {
 | 
| +    return children[index];
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// The whole JavaScript output.
 | 
| +class OutputStructure extends OutputEntity {
 | 
| +  final List<CodeLine> lines;
 | 
| +  final int headerEnd;
 | 
| +  final int footerStart;
 | 
| +  final List<LibraryBlock> children;
 | 
| +
 | 
| +  OutputStructure(
 | 
| +      this.lines,
 | 
| +      this.headerEnd,
 | 
| +      this.footerStart,
 | 
| +      this.children);
 | 
| +
 | 
| +  Interval get interval => new Interval(0, lines.length);
 | 
| +
 | 
| +  Interval get header => new Interval(0, headerEnd);
 | 
| +
 | 
| +  Interval get footer => new Interval(footerStart, lines.length);
 | 
| +
 | 
| +  /// Compute the structure of the JavaScript [lines].
 | 
| +  static OutputStructure parse(List<CodeLine> lines) {
 | 
| +
 | 
| +    int findHeaderStart(List<CodeLine> lines) {
 | 
| +      int index = 0;
 | 
| +      for (CodeLine line in lines) {
 | 
| +        if (line.code.startsWith(HEAD)) {
 | 
| +          return index;
 | 
| +        }
 | 
| +        index++;
 | 
| +      }
 | 
| +      return lines.length;
 | 
| +    }
 | 
| +
 | 
| +    int findHeaderEnd(int start, List<CodeLine> lines) {
 | 
| +      int index = start;
 | 
| +      for (CodeLine line in lines.skip(start)) {
 | 
| +        if (line.code.startsWith(END)) {
 | 
| +          return index;
 | 
| +        }
 | 
| +        index++;
 | 
| +      }
 | 
| +      return lines.length;
 | 
| +    }
 | 
| +
 | 
| +    String readHeader(CodeLine line) {
 | 
| +      String code = line.code;
 | 
| +      String ssaLineHeader;
 | 
| +      if (code.startsWith(HEAD)) {
 | 
| +        return code.substring(HEAD.length);
 | 
| +      } else if (code.startsWith(TAIL)) {
 | 
| +        return code.substring(TAIL.length);
 | 
| +      }
 | 
| +      return null;
 | 
| +    }
 | 
| +
 | 
| +    List<LibraryBlock> computeHeaderMap(
 | 
| +        List<CodeLine> lines, int start, int end) {
 | 
| +      List<LibraryBlock> libraryBlocks = <LibraryBlock>[];
 | 
| +      LibraryBlock current;
 | 
| +      for (int index = start; index < end; index++) {
 | 
| +        String header = readHeader(lines[index]);
 | 
| +        if (header != null) {
 | 
| +          if (current != null) {
 | 
| +            current.to = index;
 | 
| +          }
 | 
| +          libraryBlocks.add(current = new LibraryBlock(header, index));
 | 
| +        }
 | 
| +      }
 | 
| +      if (current != null) {
 | 
| +        current.to = end;
 | 
| +      }
 | 
| +      return libraryBlocks;
 | 
| +    }
 | 
| +
 | 
| +    int headerEnd = findHeaderStart(lines);
 | 
| +    int footerStart = findHeaderEnd(headerEnd, lines);
 | 
| +    List<LibraryBlock> libraryBlocks =
 | 
| +        computeHeaderMap(lines, headerEnd, footerStart);
 | 
| +    for (LibraryBlock block in libraryBlocks) {
 | 
| +      block.preprocess(lines);
 | 
| +    }
 | 
| +
 | 
| +    return new OutputStructure(
 | 
| +        lines, headerEnd, footerStart, libraryBlocks);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +abstract class AbstractEntity extends OutputEntity {
 | 
| +  final String name;
 | 
| +  final int from;
 | 
| +  int to;
 | 
| +
 | 
| +  AbstractEntity(this.name, this.from);
 | 
| +
 | 
| +  Interval get interval => new Interval(from, to);
 | 
| +}
 | 
| +
 | 
| +/// A block defining the content of a Dart library.
 | 
| +class LibraryBlock extends AbstractEntity {
 | 
| +  List<BasicEntity> children = <BasicEntity>[];
 | 
| +  int get headerEnd => from + 2;
 | 
| +  int get footerStart => to - 1;
 | 
| +
 | 
| +  LibraryBlock(String name, int from) : super(name, from);
 | 
| +
 | 
| +  Interval get header => new Interval(from, headerEnd);
 | 
| +
 | 
| +  Interval get footer => new Interval(footerStart, to);
 | 
| +
 | 
| +  void preprocess(List<CodeLine> lines) {
 | 
| +    int index = headerEnd;
 | 
| +    BasicEntity current;
 | 
| +    while (index < footerStart) {
 | 
| +      String line = lines[index].code;
 | 
| +      BasicEntity next;
 | 
| +      Match matchFunction = TOP_LEVEL_FUNCTION.firstMatch(line);
 | 
| +      if (matchFunction != null) {
 | 
| +        next = new BasicEntity(matchFunction.group(1), index);
 | 
| +      } else {
 | 
| +        Match matchClass = TOP_LEVEL_CLASS.firstMatch(line);
 | 
| +        if (matchClass != null) {
 | 
| +          next = new LibraryClass(matchClass.group(1), index);
 | 
| +        } else {
 | 
| +          Match matchValue = TOP_LEVEL_VALUE.firstMatch(line);
 | 
| +          if (matchValue != null) {
 | 
| +            next = new BasicEntity(matchValue.group(1), index);
 | 
| +          }
 | 
| +        }
 | 
| +      }
 | 
| +      if (next != null) {
 | 
| +        if (current != null) {
 | 
| +          current.to = index;
 | 
| +        }
 | 
| +        children.add(current = next);
 | 
| +      } else if (index == headerEnd) {
 | 
| +        throw 'Failed to match first library block line:\n$line';
 | 
| +      }
 | 
| +
 | 
| +      index++;
 | 
| +    }
 | 
| +    if (current != null) {
 | 
| +      current.to = footerStart;
 | 
| +    }
 | 
| +
 | 
| +    for (BasicEntity entity in children) {
 | 
| +      entity.preprocess(lines);
 | 
| +    }
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// A simple member of a library or class.
 | 
| +class BasicEntity extends AbstractEntity {
 | 
| +  BasicEntity(String name, int from) : super(name, from);
 | 
| +
 | 
| +  Interval get header => new Interval(from, to);
 | 
| +
 | 
| +  Interval get footer => new Interval(to, to);
 | 
| +
 | 
| +  List<OutputEntity> get children => const <OutputEntity>[];
 | 
| +
 | 
| +  void preprocess(List<CodeLine> lines) {}
 | 
| +}
 | 
| +
 | 
| +/// A block defining a Dart class.
 | 
| +class LibraryClass extends BasicEntity {
 | 
| +  List<BasicEntity> children = <BasicEntity>[];
 | 
| +  int get headerEnd => from + 1;
 | 
| +  int get footerStart => to - 1;
 | 
| +
 | 
| +  LibraryClass(String name, int from) : super(name, from);
 | 
| +
 | 
| +  Interval get header => new Interval(from, headerEnd);
 | 
| +
 | 
| +  Interval get footer => new Interval(footerStart, to);
 | 
| +
 | 
| +  void preprocess(List<CodeLine> lines) {
 | 
| +    int index = headerEnd;
 | 
| +    BasicEntity current;
 | 
| +    while (index < footerStart) {
 | 
| +      String line = lines[index].code;
 | 
| +      BasicEntity next;
 | 
| +      Match matchFunction = MEMBER_FUNCTION.firstMatch(line);
 | 
| +      if (matchFunction != null) {
 | 
| +        next = new BasicEntity(matchFunction.group(1), index);
 | 
| +      } else {
 | 
| +        Match matchClass = MEMBER_OBJECT.firstMatch(line);
 | 
| +        if (matchClass != null) {
 | 
| +          next = new BasicEntity(matchClass.group(1), index);
 | 
| +        } else {
 | 
| +          Match matchValue = MEMBER_VALUE.firstMatch(line);
 | 
| +          if (matchValue != null) {
 | 
| +            next = new BasicEntity(matchValue.group(1), index);
 | 
| +          }
 | 
| +        }
 | 
| +      }
 | 
| +      if (next != null) {
 | 
| +        if (current != null) {
 | 
| +          current.to = index;
 | 
| +        }
 | 
| +        children.add(current = next);
 | 
| +      } else if (index == headerEnd) {
 | 
| +        throw 'Failed to match first library block line:\n$line';
 | 
| +      }
 | 
| +
 | 
| +      index++;
 | 
| +    }
 | 
| +    if (current != null) {
 | 
| +      current.to = footerStart;
 | 
| +    }
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +class Interval {
 | 
| +  final int from;
 | 
| +  final int to;
 | 
| +
 | 
| +  const Interval(this.from, this.to);
 | 
| +
 | 
| +  int get length => to - from;
 | 
| +}
 | 
| +
 | 
| +class HtmlPart {
 | 
| +  void printHtmlOn(StringBuffer buffer) {}
 | 
| +}
 | 
| +
 | 
| +class ConstHtmlPart implements HtmlPart {
 | 
| +  final String html;
 | 
| +
 | 
| +  const ConstHtmlPart(this.html);
 | 
| +
 | 
| +  @override
 | 
| +  void printHtmlOn(StringBuffer buffer) {
 | 
| +    buffer.write(html);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +class CodeLineHtmlPart implements HtmlPart {
 | 
| +  final CodeLine line;
 | 
| +
 | 
| +  CodeLineHtmlPart(this.line);
 | 
| +
 | 
| +  @override
 | 
| +  void printHtmlOn(StringBuffer buffer, [int lineNoWidth]) {
 | 
| +    line.printHtmlOn(buffer, lineNoWidth);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Convert [parts] to an HTML string while checking invariants for [lines].
 | 
| +String htmlPartsToString(List<HtmlPart> parts, List<CodeLine> lines) {
 | 
| +  int lineNoWidth;
 | 
| +  if (lines.isNotEmpty) {
 | 
| +    lineNoWidth = '${lines.last.lineNo + 1}'.length;
 | 
| +  }
 | 
| +  StringBuffer buffer = new StringBuffer();
 | 
| +  int expectedLineNo = 0;
 | 
| +  for (HtmlPart part in parts) {
 | 
| +    if (part is CodeLineHtmlPart) {
 | 
| +      if (part.line.lineNo != expectedLineNo) {
 | 
| +        print('Expected line no $expectedLineNo, found ${part.line.lineNo}');
 | 
| +        if (part.line.lineNo < expectedLineNo) {
 | 
| +          print('Duplicate lines:');
 | 
| +          int index = part.line.lineNo;
 | 
| +          while (index <= expectedLineNo) {
 | 
| +            print(lines[index++].code);
 | 
| +          }
 | 
| +        } else {
 | 
| +          print('Missing lines:');
 | 
| +          int index = expectedLineNo;
 | 
| +          while (index <= part.line.lineNo) {
 | 
| +            print(lines[index++].code);
 | 
| +          }
 | 
| +        }
 | 
| +        expectedLineNo = part.line.lineNo;
 | 
| +      }
 | 
| +      expectedLineNo++;
 | 
| +      part.printHtmlOn(buffer, lineNoWidth);
 | 
| +    } else {
 | 
| +      part.printHtmlOn(buffer);
 | 
| +    }
 | 
| +  }
 | 
| +  return buffer.toString();
 | 
| +}
 | 
| +
 | 
| +class CodeLinesResult {
 | 
| +  final List<CodeLine> codeLines;
 | 
| +  final Coverage coverage;
 | 
| +
 | 
| +  CodeLinesResult(this.codeLines, this.coverage);
 | 
| +}
 | 
| +
 | 
| +/// Compute [CodeLine]s and [Coverage] for [filename] using the given [options].
 | 
| +Future<CodeLinesResult> computeCodeLines(
 | 
| +    List<String> options,
 | 
| +    String filename) async {
 | 
| +  SourceMapProcessor processor = new SourceMapProcessor(filename);
 | 
| +  List<SourceMapInfo> sourceMapInfoList =
 | 
| +      await processor.process(options, perElement: false);
 | 
| +
 | 
| +  const int WITH_SOURCE_INFO = 0;
 | 
| +  const int WITHOUT_SOURCE_INFO = 1;
 | 
| +  const int ADDITIONAL_SOURCE_INFO = 2;
 | 
| +
 | 
| +  for (SourceMapInfo info in sourceMapInfoList) {
 | 
| +    if (info.element != null) continue;
 | 
| +
 | 
| +    List<CodeLine> codeLines;
 | 
| +    Coverage coverage = new Coverage();
 | 
| +    List<Annotation> annotations = <Annotation>[];
 | 
| +    String code = info.code;
 | 
| +    TraceGraph graph = createTraceGraph(info, coverage);
 | 
| +    Set<js.Node> mappedNodes = new Set<js.Node>();
 | 
| +    for (TraceStep step in graph.steps) {
 | 
| +      int offset;
 | 
| +      if (options.contains(USE_NEW_SOURCE_INFO)) {
 | 
| +        offset = step.offset.codeOffset;
 | 
| +      } else {
 | 
| +        offset = info.jsCodePositions[step.node].startPosition;
 | 
| +      }
 | 
| +      if (offset != null) {
 | 
| +        int id = step.sourceLocation != null
 | 
| +            ? WITH_SOURCE_INFO : WITHOUT_SOURCE_INFO;
 | 
| +        annotations.add(
 | 
| +            new Annotation(id, offset, null));
 | 
| +      }
 | 
| +    }
 | 
| +    if (!options.contains(USE_NEW_SOURCE_INFO)) {
 | 
| +      for (js.Node node in info.nodeMap.nodes) {
 | 
| +        if (!mappedNodes.contains(node)) {
 | 
| +          int offset = info.jsCodePositions[node].startPosition;
 | 
| +          annotations.add(
 | 
| +                      new Annotation(ADDITIONAL_SOURCE_INFO, offset, null));
 | 
| +        }
 | 
| +      }
 | 
| +    }
 | 
| +    codeLines = convertAnnotatedCodeToCodeLines(
 | 
| +        code,
 | 
| +        annotations,
 | 
| +        colorScheme: new CustomColorScheme(
 | 
| +          single: (int id) {
 | 
| +            if (id == WITH_SOURCE_INFO) {
 | 
| +              return WITH_SOURCE_INFO_STYLE;
 | 
| +            } else if (id == ADDITIONAL_SOURCE_INFO) {
 | 
| +              return ADDITIONAL_SOURCE_INFO_STYLE;
 | 
| +            }
 | 
| +            return WITHOUT_SOURCE_INFO_STYLE;
 | 
| +          },
 | 
| +          multi: (List ids) {
 | 
| +            if (ids.contains(WITH_SOURCE_INFO)) {
 | 
| +              return WITH_SOURCE_INFO_STYLE;
 | 
| +            } else if (ids.contains(ADDITIONAL_SOURCE_INFO)) {
 | 
| +              return ADDITIONAL_SOURCE_INFO_STYLE;
 | 
| +            }
 | 
| +            return WITHOUT_SOURCE_INFO_STYLE;
 | 
| +          }
 | 
| +        ));
 | 
| +    return new CodeLinesResult(codeLines, coverage);
 | 
| +  }
 | 
| +}
 | 
| 
 |