| Index: tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart
 | 
| diff --git a/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart b/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart
 | 
| index 9cb53e566d977cf1e567ef943f6dc7ee9507ba74..123cbe2a918ac1e427c2b116d9d8a5ad7422ab7a 100644
 | 
| --- a/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart
 | 
| +++ b/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart
 | 
| @@ -11,8 +11,10 @@ import 'package:compiler/src/null_compiler_output.dart' show NullSink;
 | 
|  import 'package:compiler/src/elements/elements.dart';
 | 
|  import 'package:compiler/src/helpers/helpers.dart';
 | 
|  import 'package:compiler/src/filenames.dart';
 | 
| +import 'package:compiler/src/io/code_output.dart';
 | 
|  import 'package:compiler/src/io/source_file.dart';
 | 
|  import 'package:compiler/src/io/source_information.dart';
 | 
| +import 'package:compiler/src/io/position_information.dart';
 | 
|  import 'package:compiler/src/js/js.dart' as js;
 | 
|  import 'package:compiler/src/js/js_debug.dart';
 | 
|  import 'package:compiler/src/js/js_source_mapping.dart';
 | 
| @@ -21,15 +23,51 @@ import 'package:compiler/src/source_file_provider.dart';
 | 
|  import '../memory_compiler.dart';
 | 
|  import '../output_collector.dart';
 | 
|  
 | 
| +class SourceFileSink implements EventSink<String> {
 | 
| +  final String filename;
 | 
| +  StringBuffer sb = new StringBuffer();
 | 
| +  SourceFile sourceFile;
 | 
| +
 | 
| +  SourceFileSink(this.filename);
 | 
| +
 | 
| +  @override
 | 
| +  void add(String event) {
 | 
| +    sb.write(event);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  void addError(errorEvent, [StackTrace stackTrace]) {
 | 
| +    // Ignore.
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  void close() {
 | 
| +    sourceFile = new StringSourceFile.fromName(filename, sb.toString());
 | 
| +  }
 | 
| +}
 | 
| +
 | 
|  class OutputProvider implements CompilerOutput {
 | 
| -  BufferedEventSink jsMapOutput;
 | 
| +  Map<Uri, SourceFileSink> outputMap = <Uri, SourceFileSink>{};
 | 
| +
 | 
| +  SourceFile getSourceFile(Uri uri) {
 | 
| +    SourceFileSink sink = outputMap[uri];
 | 
| +    if (sink != null) {
 | 
| +      return sink.sourceFile;
 | 
| +    }
 | 
| +    return null;
 | 
| +  }
 | 
| +
 | 
| +  SourceFileSink createSourceFileSink(String name, String extension) {
 | 
| +    String filename = '$name.$extension';
 | 
| +    SourceFileSink sink = new SourceFileSink(filename);
 | 
| +    Uri uri = Uri.parse(filename);
 | 
| +    outputMap[uri] = sink;
 | 
| +    return sink;
 | 
| +  }
 | 
|  
 | 
|    @override
 | 
|    EventSink<String> createEventSink(String name, String extension) {
 | 
| -    if (extension == 'js.map') {
 | 
| -      return jsMapOutput = new BufferedEventSink();
 | 
| -    }
 | 
| -    return new NullSink('$name.$extension');
 | 
| +    return createSourceFileSink(name, extension);
 | 
|    }
 | 
|  }
 | 
|  
 | 
| @@ -42,11 +80,8 @@ class CloningOutputProvider extends OutputProvider {
 | 
|    @override
 | 
|    EventSink<String> createEventSink(String name, String extension) {
 | 
|      EventSink<String> output = outputProvider(name, extension);
 | 
| -    if (extension == 'js.map') {
 | 
| -      output = new CloningEventSink(
 | 
| -          [output, jsMapOutput = new BufferedEventSink()]);
 | 
| -    }
 | 
| -    return output;
 | 
| +    return new CloningEventSink(
 | 
| +        [output, createSourceFileSink(name, extension)]);
 | 
|    }
 | 
|  }
 | 
|  
 | 
| @@ -56,17 +91,23 @@ abstract class SourceFileManager {
 | 
|  
 | 
|  class ProviderSourceFileManager implements SourceFileManager {
 | 
|    final SourceFileProvider sourceFileProvider;
 | 
| +  final OutputProvider outputProvider;
 | 
|  
 | 
| -  ProviderSourceFileManager(this.sourceFileProvider);
 | 
| +  ProviderSourceFileManager(this.sourceFileProvider, this.outputProvider);
 | 
|  
 | 
|    @override
 | 
|    SourceFile getSourceFile(uri) {
 | 
| -    return sourceFileProvider.getSourceFile(uri);
 | 
| +    SourceFile sourceFile = sourceFileProvider.getSourceFile(uri);
 | 
| +    if (sourceFile == null) {
 | 
| +      sourceFile = outputProvider.getSourceFile(uri);
 | 
| +    }
 | 
| +    return sourceFile;
 | 
|    }
 | 
|  }
 | 
|  
 | 
|  class RecordingPrintingContext extends LenientPrintingContext {
 | 
|    CodePositionListener listener;
 | 
| +  Map<js.Node, CodePosition> codePositions = <js.Node, CodePosition>{};
 | 
|  
 | 
|    RecordingPrintingContext(this.listener);
 | 
|  
 | 
| @@ -75,11 +116,163 @@ class RecordingPrintingContext extends LenientPrintingContext {
 | 
|                  int startPosition,
 | 
|                  int endPosition,
 | 
|                  int closingPosition) {
 | 
| +    codePositions[node] =
 | 
| +        new CodePosition(startPosition, endPosition, closingPosition);
 | 
|      listener.onPositions(
 | 
|          node, startPosition, endPosition, closingPosition);
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +/// A [SourceMapper] that records the source locations on each node.
 | 
| +class RecordingSourceMapper implements SourceMapper {
 | 
| +  final SourceMapper sourceMapper;
 | 
| +  final _LocationRecorder nodeToSourceLocationsMap;
 | 
| +
 | 
| +  RecordingSourceMapper(this.sourceMapper, this.nodeToSourceLocationsMap);
 | 
| +
 | 
| +  @override
 | 
| +  void register(js.Node node, int codeOffset, SourceLocation sourceLocation) {
 | 
| +    nodeToSourceLocationsMap.register(node, codeOffset, sourceLocation);
 | 
| +    sourceMapper.register(node, codeOffset, sourceLocation);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// A wrapper of [SourceInformationProcessor] that records source locations and
 | 
| +/// code positions.
 | 
| +class RecordingSourceInformationProcessor
 | 
| +    implements SourceInformationProcessor {
 | 
| +  final RecordingSourceInformationStrategy wrapper;
 | 
| +  final SourceInformationProcessor processor;
 | 
| +  final CodePositionRecorder codePositions;
 | 
| +  final LocationMap nodeToSourceLocationsMap;
 | 
| +
 | 
| +  RecordingSourceInformationProcessor(
 | 
| +      this.wrapper,
 | 
| +      this.processor,
 | 
| +      this.codePositions,
 | 
| +      this.nodeToSourceLocationsMap);
 | 
| +
 | 
| +  @override
 | 
| +  void onPositions(js.Node node,
 | 
| +                   int startPosition,
 | 
| +                   int endPosition,
 | 
| +                   int closingPosition) {
 | 
| +    codePositions.registerPositions(
 | 
| +        node, startPosition, endPosition, closingPosition);
 | 
| +    processor.onPositions(node, startPosition, endPosition, closingPosition);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  void process(js.Node node, BufferedCodeOutput code) {
 | 
| +    processor.process(node, code);
 | 
| +    wrapper.registerProcess(
 | 
| +        node, code, codePositions, nodeToSourceLocationsMap);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Information recording for a use of [SourceInformationProcessor].
 | 
| +class RecordedSourceInformationProcess {
 | 
| +  final js.Node root;
 | 
| +  final String code;
 | 
| +  final CodePositionRecorder codePositions;
 | 
| +  final LocationMap nodeToSourceLocationsMap;
 | 
| +
 | 
| +  RecordedSourceInformationProcess(
 | 
| +      this.root,
 | 
| +      this.code,
 | 
| +      this.codePositions,
 | 
| +      this.nodeToSourceLocationsMap);
 | 
| +}
 | 
| +
 | 
| +
 | 
| +/// A wrapper of [JavaScriptSourceInformationStrategy] that records
 | 
| +/// [RecordedSourceInformationProcess].
 | 
| +class RecordingSourceInformationStrategy
 | 
| +    extends JavaScriptSourceInformationStrategy {
 | 
| +  final JavaScriptSourceInformationStrategy strategy;
 | 
| +  final Map<RecordedSourceInformationProcess, js.Node> processMap =
 | 
| +      <RecordedSourceInformationProcess, js.Node>{};
 | 
| +  final Map<js.Node, RecordedSourceInformationProcess> nodeMap =
 | 
| +      <js.Node, RecordedSourceInformationProcess>{};
 | 
| +
 | 
| +  RecordingSourceInformationStrategy(this.strategy);
 | 
| +
 | 
| +  @override
 | 
| +  SourceInformationBuilder createBuilderForContext(AstElement element) {
 | 
| +    return strategy.createBuilderForContext(element);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  SourceInformationProcessor createProcessor(SourceMapper sourceMapper) {
 | 
| +    LocationMap nodeToSourceLocationsMap =
 | 
| +        new _LocationRecorder();
 | 
| +    CodePositionRecorder codePositions = new CodePositionRecorder();
 | 
| +    return new RecordingSourceInformationProcessor(
 | 
| +        this,
 | 
| +        strategy.createProcessor(new RecordingSourceMapper(
 | 
| +            sourceMapper, nodeToSourceLocationsMap)),
 | 
| +            codePositions, nodeToSourceLocationsMap);
 | 
| +  }
 | 
| +
 | 
| +  void registerProcess(js.Node root,
 | 
| +                       BufferedCodeOutput code,
 | 
| +                       CodePositionRecorder codePositions,
 | 
| +                       LocationMap nodeToSourceLocationsMap) {
 | 
| +    RecordedSourceInformationProcess subProcess =
 | 
| +        new RecordedSourceInformationProcess(
 | 
| +            root, code.getText(), codePositions, nodeToSourceLocationsMap);
 | 
| +    processMap[subProcess] = root;
 | 
| +  }
 | 
| +
 | 
| +  RecordedSourceInformationProcess subProcessForNode(js.Node node) {
 | 
| +    return nodeMap.putIfAbsent(node, () {
 | 
| +      for (RecordedSourceInformationProcess subProcess in processMap.keys) {
 | 
| +        js.Node root = processMap[subProcess];
 | 
| +        FindVisitor visitor = new FindVisitor(node);
 | 
| +        root.accept(visitor);
 | 
| +        if (visitor.found) {
 | 
| +          return new RecordedSourceInformationProcess(
 | 
| +              node,
 | 
| +              subProcess.code,
 | 
| +              subProcess.codePositions,
 | 
| +              new _FilteredLocationMap(
 | 
| +                  visitor.nodes, subProcess.nodeToSourceLocationsMap));
 | 
| +        }
 | 
| +        return null;
 | 
| +      }
 | 
| +    });
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Visitor that collects all nodes that are within a function. Used by the
 | 
| +/// [RecordingSourceInformationStrategy] to filter what is recorded in a
 | 
| +/// [RecordedSourceInformationProcess].
 | 
| +class FindVisitor extends js.BaseVisitor {
 | 
| +  final js.Node soughtNode;
 | 
| +  bool found = false;
 | 
| +  bool add = false;
 | 
| +  final Set<js.Node> nodes = new Set<js.Node>();
 | 
| +
 | 
| +  FindVisitor(this.soughtNode);
 | 
| +
 | 
| +  visitNode(js.Node node) {
 | 
| +    if (node == soughtNode) {
 | 
| +      found = true;
 | 
| +      add = true;
 | 
| +    }
 | 
| +    if (add) {
 | 
| +      nodes.add(node);
 | 
| +    }
 | 
| +    node.visitChildren(this);
 | 
| +    if (node == soughtNode) {
 | 
| +      add = false;
 | 
| +    }
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +const String USE_NEW_SOURCE_INFO =  '--use-new-source-info';
 | 
| +const String DISABLE_INLINING = '--disable-inlining';
 | 
| +
 | 
|  /// Processor that computes [SourceMapInfo] for the JavaScript compiled for a
 | 
|  /// given Dart file.
 | 
|  class SourceMapProcessor {
 | 
| @@ -112,11 +305,12 @@ class SourceMapProcessor {
 | 
|    /// Computes the [SourceMapInfo] for the compiled elements.
 | 
|    Future<List<SourceMapInfo>> process(
 | 
|        List<String> options,
 | 
| -      {bool verbose: true}) async {
 | 
| +      {bool verbose: true,
 | 
| +       bool perElement: true}) async {
 | 
|      OutputProvider outputProvider = outputToFile
 | 
| -        ? new OutputProvider()
 | 
| -        : new CloningOutputProvider(targetUri, sourceMapFileUri);
 | 
| -    if (options.contains('--use-new-source-info')) {
 | 
| +        ? new CloningOutputProvider(targetUri, sourceMapFileUri)
 | 
| +        : new OutputProvider();
 | 
| +    if (options.contains(USE_NEW_SOURCE_INFO)) {
 | 
|        if (verbose) print('Using the new source information system.');
 | 
|        useNewSourceInfo = true;
 | 
|      }
 | 
| @@ -125,7 +319,7 @@ class SourceMapProcessor {
 | 
|          // TODO(johnniwinther): Use [verbose] to avoid showing diagnostics.
 | 
|          options: ['--out=$targetUri', '--source-map=$sourceMapFileUri']
 | 
|              ..addAll(options));
 | 
| -    if (options.contains('--disable-inlining')) {
 | 
| +    if (options.contains(DISABLE_INLINING)) {
 | 
|        if (verbose) print('Inlining disabled');
 | 
|        compiler.disableInlining = true;
 | 
|      }
 | 
| @@ -133,30 +327,58 @@ class SourceMapProcessor {
 | 
|      JavaScriptBackend backend = compiler.backend;
 | 
|      var handler = compiler.handler;
 | 
|      SourceFileProvider sourceFileProvider = handler.provider;
 | 
| -    sourceFileManager = new ProviderSourceFileManager(sourceFileProvider);
 | 
| +    sourceFileManager = new ProviderSourceFileManager(
 | 
| +        sourceFileProvider,
 | 
| +        outputProvider);
 | 
| +    RecordingSourceInformationStrategy strategy =
 | 
| +        new RecordingSourceInformationStrategy(backend.sourceInformationStrategy);
 | 
| +    backend.sourceInformationStrategy = strategy;
 | 
|      await compiler.run(inputUri);
 | 
|  
 | 
|      List<SourceMapInfo> infoList = <SourceMapInfo>[];
 | 
| -    backend.generatedCode.forEach((Element element, js.Expression node) {
 | 
| -      js.JavaScriptPrintingOptions options =
 | 
| -          new js.JavaScriptPrintingOptions();
 | 
| -      JavaScriptSourceInformationStrategy sourceInformationStrategy =
 | 
| -          compiler.backend.sourceInformationStrategy;
 | 
| -      NodeToSourceLocationsMap nodeMap = new NodeToSourceLocationsMap();
 | 
| -      SourceInformationProcessor sourceInformationProcessor =
 | 
| -          sourceInformationStrategy.createProcessor(nodeMap);
 | 
| -      RecordingPrintingContext printingContext =
 | 
| -          new RecordingPrintingContext(sourceInformationProcessor);
 | 
| -      new js.Printer(options, printingContext).visit(node);
 | 
| -      sourceInformationProcessor.process(node);
 | 
| -
 | 
| -      String code = printingContext.getText();
 | 
| +    if (perElement) {
 | 
| +      backend.generatedCode.forEach((Element element, js.Expression node) {
 | 
| +        RecordedSourceInformationProcess subProcess =
 | 
| +            strategy.subProcessForNode(node);
 | 
| +        if (subProcess == null) {
 | 
| +          // TODO(johnniwinther): Find out when this is happening and if it
 | 
| +          // is benign. (Known to happen for `bool#fromString`)
 | 
| +          print('No subProcess found for $element');
 | 
| +          return;
 | 
| +        }
 | 
| +        LocationMap nodeMap = subProcess.nodeToSourceLocationsMap;
 | 
| +        String code = subProcess.code;
 | 
| +        CodePositionRecorder codePositions = subProcess.codePositions;
 | 
| +        CodePointComputer visitor =
 | 
| +            new CodePointComputer(sourceFileManager, code, nodeMap);
 | 
| +        visitor.apply(node);
 | 
| +        List<CodePoint> codePoints = visitor.codePoints;
 | 
| +        infoList.add(new SourceMapInfo(
 | 
| +            element, code, node,
 | 
| +            codePoints,
 | 
| +            codePositions/*strategy.codePositions*/,
 | 
| +            nodeMap));
 | 
| +      });
 | 
| +    } else {
 | 
| +      // TODO(johnniwinther): Supported multiple output units.
 | 
| +      RecordedSourceInformationProcess process = strategy.processMap.keys.first;
 | 
| +      js.Node node = strategy.processMap[process];
 | 
| +      String code;
 | 
| +      LocationMap nodeMap;
 | 
| +      CodePositionRecorder codePositions;
 | 
| +      nodeMap = process.nodeToSourceLocationsMap;
 | 
| +      code = process.code;
 | 
| +      codePositions = process.codePositions;
 | 
|        CodePointComputer visitor =
 | 
|            new CodePointComputer(sourceFileManager, code, nodeMap);
 | 
|        visitor.apply(node);
 | 
|        List<CodePoint> codePoints = visitor.codePoints;
 | 
| -      infoList.add(new SourceMapInfo(element, code, node, codePoints, nodeMap));
 | 
| -    });
 | 
| +      infoList.add(new SourceMapInfo(
 | 
| +          null, code, node,
 | 
| +          codePoints,
 | 
| +          codePositions,
 | 
| +          nodeMap));
 | 
| +    }
 | 
|  
 | 
|      return infoList;
 | 
|    }
 | 
| @@ -167,19 +389,43 @@ class SourceMapInfo {
 | 
|    final String name;
 | 
|    final Element element;
 | 
|    final String code;
 | 
| -  final js.Expression node;
 | 
| +  final js.Node node;
 | 
|    final List<CodePoint> codePoints;
 | 
| -  final NodeToSourceLocationsMap nodeMap;
 | 
| +  final CodePositionMap jsCodePositions;
 | 
| +  final LocationMap nodeMap;
 | 
|  
 | 
|    SourceMapInfo(
 | 
| -      Element element, this.code, this.node, this.codePoints, this.nodeMap)
 | 
| -      : this.name = computeElementNameForSourceMaps(element),
 | 
| +      Element element,
 | 
| +      this.code,
 | 
| +      this.node,
 | 
| +      this.codePoints,
 | 
| +      this.jsCodePositions,
 | 
| +      this.nodeMap)
 | 
| +      : this.name =
 | 
| +          element != null ? computeElementNameForSourceMaps(element) : '',
 | 
|          this.element = element;
 | 
| +
 | 
| +  String toString() {
 | 
| +    return '$name:$element';
 | 
| +  }
 | 
|  }
 | 
|  
 | 
|  /// Collection of JavaScript nodes with their source mapped target offsets
 | 
|  /// and source locations.
 | 
| -class NodeToSourceLocationsMap implements SourceMapper {
 | 
| +abstract class LocationMap {
 | 
| +  Iterable<js.Node> get nodes;
 | 
| +
 | 
| +  Map<int, List<SourceLocation>> operator[] (js.Node node);
 | 
| +
 | 
| +  factory LocationMap.recorder() = _LocationRecorder;
 | 
| +
 | 
| +  factory LocationMap.filter(Set<js.Node> nodes, LocationMap map) =
 | 
| +      _FilteredLocationMap;
 | 
| +
 | 
| +}
 | 
| +
 | 
| +class _LocationRecorder
 | 
| +    implements SourceMapper, LocationMap {
 | 
|    final Map<js.Node, Map<int, List<SourceLocation>>> _nodeMap = {};
 | 
|  
 | 
|    @override
 | 
| @@ -196,11 +442,25 @@ class NodeToSourceLocationsMap implements SourceMapper {
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +class _FilteredLocationMap implements LocationMap {
 | 
| +  final Set<js.Node> _nodes;
 | 
| +  final LocationMap map;
 | 
| +
 | 
| +  _FilteredLocationMap(this._nodes, this.map);
 | 
| +
 | 
| +  Iterable<js.Node> get nodes => map.nodes.where((n) => _nodes.contains(n));
 | 
| +
 | 
| +  Map<int, List<SourceLocation>> operator[] (js.Node node) {
 | 
| +    return map[node];
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +
 | 
|  /// Visitor that computes the [CodePoint]s for source mapping locations.
 | 
|  class CodePointComputer extends js.BaseVisitor {
 | 
|    final SourceFileManager sourceFileManager;
 | 
|    final String code;
 | 
| -  final NodeToSourceLocationsMap nodeMap;
 | 
| +  final LocationMap nodeMap;
 | 
|    List<CodePoint> codePoints = [];
 | 
|  
 | 
|    CodePointComputer(this.sourceFileManager, this.code, this.nodeMap);
 | 
| 
 |