| Index: pkg/compiler/lib/src/io/position_information.dart
|
| diff --git a/pkg/compiler/lib/src/io/position_information.dart b/pkg/compiler/lib/src/io/position_information.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9b91cab1e6cba46075bb56377e0988984ba6f3ba
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/io/position_information.dart
|
| @@ -0,0 +1,439 @@
|
| +// 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.
|
| +
|
| +/// Source information system mapping that attempts a semantic mapping between
|
| +/// offsets of JavaScript code points to offsets of Dart code points.
|
| +
|
| +library dart2js.source_information.position;
|
| +
|
| +import '../dart2jslib.dart' show
|
| + invariant,
|
| + MessageKind,
|
| + SourceSpan;
|
| +import '../elements/elements.dart' show
|
| + AstElement,
|
| + LocalElement;
|
| +import '../js/js.dart' as js;
|
| +import '../js/js_source_mapping.dart';
|
| +import '../js/js_debug.dart';
|
| +import '../tree/tree.dart' show Node, Send;
|
| +import '../util/util.dart' show NO_LOCATION_SPANNABLE;
|
| +
|
| +import 'source_file.dart';
|
| +import 'source_information.dart';
|
| +
|
| +/// [SourceInformation] that consists of an offset position into the source
|
| +/// code.
|
| +class PositionSourceInformation extends SourceInformation {
|
| + @override
|
| + final SourceLocation startPosition;
|
| +
|
| + @override
|
| + final SourceLocation closingPosition;
|
| +
|
| + PositionSourceInformation(this.startPosition,
|
| + [this.closingPosition]);
|
| +
|
| + @override
|
| + List<SourceLocation> get sourceLocations {
|
| + List<SourceLocation> list = <SourceLocation>[];
|
| + if (startPosition != null) {
|
| + list.add(startPosition);
|
| + }
|
| + if (closingPosition != null) {
|
| + list.add(closingPosition);
|
| + }
|
| + return list;
|
| + }
|
| +
|
| + @override
|
| + SourceSpan get sourceSpan {
|
| + SourceLocation location =
|
| + startPosition != null ? startPosition : closingPosition;
|
| + Uri uri = location.sourceUri;
|
| + int offset = location.offset;
|
| + return new SourceSpan(uri, offset, offset);
|
| + }
|
| +
|
| + int get hashCode {
|
| + return 0x7FFFFFFF &
|
| + (startPosition.hashCode * 17 + closingPosition.hashCode * 19);
|
| + }
|
| +
|
| + bool operator ==(other) {
|
| + if (identical(this, other)) return true;
|
| + if (other is! PositionSourceInformation) return false;
|
| + return startPosition == other.startPosition &&
|
| + closingPosition == other.closingPosition;
|
| + }
|
| +
|
| + /// Create a textual representation of the source information using [uriText]
|
| + /// as the Uri representation.
|
| + String _computeText(String uriText) {
|
| + StringBuffer sb = new StringBuffer();
|
| + sb.write('$uriText:');
|
| + // Use 1-based line/column info to match usual dart tool output.
|
| + if (startPosition != null) {
|
| + sb.write('[${startPosition.line + 1},'
|
| + '${startPosition.column + 1}]');
|
| + }
|
| + if (closingPosition != null) {
|
| + sb.write('-[${closingPosition.line + 1},'
|
| + '${closingPosition.column + 1}]');
|
| + }
|
| + return sb.toString();
|
| + }
|
| +
|
| + String get shortText {
|
| + if (startPosition != null) {
|
| + return _computeText(startPosition.sourceUri.pathSegments.last);
|
| + } else {
|
| + return _computeText(closingPosition.sourceUri.pathSegments.last);
|
| + }
|
| + }
|
| +
|
| + String toString() {
|
| + if (startPosition != null) {
|
| + return _computeText('${startPosition.sourceUri}');
|
| + } else {
|
| + return _computeText('${closingPosition.sourceUri}');
|
| + }
|
| + }
|
| +}
|
| +
|
| +class PositionSourceInformationStrategy
|
| + implements JavaScriptSourceInformationStrategy {
|
| + const PositionSourceInformationStrategy();
|
| +
|
| + @override
|
| + SourceInformationBuilder createBuilderForContext(AstElement element) {
|
| + return new PositionSourceInformationBuilder(element);
|
| + }
|
| +
|
| + @override
|
| + SourceInformationProcessor createProcessor(SourceMapper mapper) {
|
| + return new PositionSourceInformationProcessor(mapper);
|
| + }
|
| +}
|
| +
|
| +/// [SourceInformationBuilder] that generates [PositionSourceInformation].
|
| +class PositionSourceInformationBuilder implements SourceInformationBuilder {
|
| + final SourceFile sourceFile;
|
| + final String name;
|
| +
|
| + PositionSourceInformationBuilder(AstElement element)
|
| + : sourceFile = element.implementation.compilationUnit.script.file,
|
| + name = computeElementNameForSourceMaps(element);
|
| +
|
| + SourceInformation buildDeclaration(AstElement element) {
|
| + if (element.isSynthesized) {
|
| + return new PositionSourceInformation(
|
| + new OffsetSourceLocation(
|
| + sourceFile, element.position.charOffset, name));
|
| + } else {
|
| + return new PositionSourceInformation(
|
| + null,
|
| + new OffsetSourceLocation(sourceFile,
|
| + element.resolvedAst.node.getEndToken().charOffset, name));
|
| + }
|
| + }
|
| +
|
| + /// Builds a source information object pointing the start position of [node].
|
| + SourceInformation buildBegin(Node node) {
|
| + return new PositionSourceInformation(new OffsetSourceLocation(
|
| + sourceFile, node.getBeginToken().charOffset, name));
|
| + }
|
| +
|
| + @override
|
| + SourceInformation buildGeneric(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformation buildReturn(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformation buildImplicitReturn(AstElement element) {
|
| + if (element.isSynthesized) {
|
| + return new PositionSourceInformation(
|
| + new OffsetSourceLocation(
|
| + sourceFile, element.position.charOffset, name));
|
| + } else {
|
| + return new PositionSourceInformation(
|
| + new OffsetSourceLocation(sourceFile,
|
| + element.resolvedAst.node.getEndToken().charOffset, name));
|
| + }
|
| + }
|
| +
|
| +
|
| + @override
|
| + SourceInformation buildLoop(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformation buildGet(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformation buildCall(Node receiver, Node call) {
|
| + return new PositionSourceInformation(
|
| + new OffsetSourceLocation(
|
| + sourceFile, receiver.getBeginToken().charOffset, name),
|
| + new OffsetSourceLocation(
|
| + sourceFile, call.getBeginToken().charOffset, name));
|
| + }
|
| +
|
| + @override
|
| + SourceInformation buildNew(Node node) {
|
| + return buildBegin(node);
|
| + }
|
| +
|
| + @override
|
| + SourceInformation buildIf(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformation buildThrow(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformation buildAssignment(Node node) => buildBegin(node);
|
| +
|
| + @override
|
| + SourceInformationBuilder forContext(AstElement element) {
|
| + return new PositionSourceInformationBuilder(element);
|
| + }
|
| +}
|
| +
|
| +/// The start, end and closing offsets for a [js.Node].
|
| +class CodePosition {
|
| + final int startPosition;
|
| + final int endPosition;
|
| + final int closingPosition;
|
| +
|
| + CodePosition(this.startPosition, this.endPosition, this.closingPosition);
|
| +}
|
| +
|
| +/// Registry for mapping [js.Node]s to their [CodePosition].
|
| +class CodePositionRecorder {
|
| + Map<js.Node, CodePosition> _codePositionMap = <js.Node, CodePosition>{};
|
| +
|
| + void registerPositions(js.Node node,
|
| + int startPosition,
|
| + int endPosition,
|
| + int closingPosition) {
|
| + registerCodePosition(node,
|
| + new CodePosition(startPosition, endPosition, closingPosition));
|
| + }
|
| +
|
| + void registerCodePosition(js.Node node, CodePosition codePosition) {
|
| + _codePositionMap[node] = codePosition;
|
| + }
|
| +
|
| + CodePosition operator [](js.Node node) => _codePositionMap[node];
|
| +}
|
| +
|
| +enum SourcePositionKind {
|
| + START,
|
| + CLOSING,
|
| + END,
|
| +}
|
| +
|
| +enum CodePositionKind {
|
| + START,
|
| + CLOSING,
|
| + END,
|
| +}
|
| +
|
| +/// Processor that associates [SourceLocation]s from [SourceInformation] on
|
| +/// [js.Node]s with the target offsets in a [SourceMapper].
|
| +class PositionSourceInformationProcessor
|
| + extends js.BaseVisitor implements SourceInformationProcessor {
|
| + final CodePositionRecorder codePositions = new CodePositionRecorder();
|
| + final SourceMapper sourceMapper;
|
| +
|
| + PositionSourceInformationProcessor(this.sourceMapper);
|
| +
|
| + void process(js.Node node) {
|
| + node.accept(this);
|
| + }
|
| +
|
| + void visitChildren(js.Node node) {
|
| + node.visitChildren(this);
|
| + }
|
| +
|
| + CodePosition getCodePosition(js.Node node) {
|
| + return codePositions[node];
|
| + }
|
| +
|
| + /// Associates [sourceInformation] with the JavaScript [node].
|
| + ///
|
| + /// The offset into the JavaScript code is computed by pulling the
|
| + /// [codePositionKind] from the code positions associated with
|
| + /// [codePositionNode].
|
| + ///
|
| + /// The mapped Dart source location is computed by pulling the
|
| + /// [sourcePositionKind] source location from [sourceInformation].
|
| + void apply(js.Node node,
|
| + js.Node codePositionNode,
|
| + CodePositionKind codePositionKind,
|
| + SourceInformation sourceInformation,
|
| + SourcePositionKind sourcePositionKind) {
|
| + if (sourceInformation != null) {
|
| + CodePosition codePosition = getCodePosition(codePositionNode);
|
| + // We should always have recorded the needed code positions.
|
| + assert(invariant(
|
| + NO_LOCATION_SPANNABLE,
|
| + codePosition != null,
|
| + message:
|
| + "Code position missing for "
|
| + "${nodeToString(codePositionNode)}:\n"
|
| + "${DebugPrinter.prettyPrint(node)}"));
|
| + if (codePosition == null) return;
|
| + int codeLocation;
|
| + SourceLocation sourceLocation;
|
| + switch (codePositionKind) {
|
| + case CodePositionKind.START:
|
| + codeLocation = codePosition.startPosition;
|
| + break;
|
| + case CodePositionKind.CLOSING:
|
| + codeLocation = codePosition.closingPosition;
|
| + break;
|
| + case CodePositionKind.END:
|
| + codeLocation = codePosition.endPosition;
|
| + break;
|
| + }
|
| + switch (sourcePositionKind) {
|
| + case SourcePositionKind.START:
|
| + sourceLocation = sourceInformation.startPosition;
|
| + break;
|
| + case SourcePositionKind.CLOSING:
|
| + sourceLocation = sourceInformation.closingPosition;
|
| + break;
|
| + case SourcePositionKind.END:
|
| + sourceLocation = sourceInformation.endPosition;
|
| + break;
|
| + }
|
| + if (codeLocation != null && sourceLocation != null) {
|
| + sourceMapper.register(node, codeLocation, sourceLocation);
|
| + }
|
| + }
|
| + }
|
| +
|
| + @override
|
| + visitNode(js.Node node) {
|
| + SourceInformation sourceInformation = node.sourceInformation;
|
| + if (sourceInformation != null) {
|
| + /// Associates the left-most position of the JS code with the left-most
|
| + /// position of the Dart code.
|
| + apply(node,
|
| + node, CodePositionKind.START,
|
| + sourceInformation, SourcePositionKind.START);
|
| + }
|
| + visitChildren(node);
|
| + }
|
| +
|
| + @override
|
| + visitFun(js.Fun node) {
|
| + SourceInformation sourceInformation = node.sourceInformation;
|
| + if (sourceInformation != null) {
|
| + /// Associates the end brace of the JavaScript function with the end brace
|
| + /// of the Dart function (or the `;` in case of arrow notation).
|
| + apply(node,
|
| + node, CodePositionKind.CLOSING,
|
| + sourceInformation, SourcePositionKind.CLOSING);
|
| + }
|
| +
|
| + visitChildren(node);
|
| + }
|
| +
|
| + @override
|
| + visitExpressionStatement(js.ExpressionStatement node) {
|
| + visitChildren(node);
|
| + }
|
| +
|
| + @override
|
| + visitBinary(js.Binary node) {
|
| + visitChildren(node);
|
| + }
|
| +
|
| + @override
|
| + visitAccess(js.PropertyAccess node) {
|
| + visitChildren(node);
|
| + }
|
| +
|
| + @override
|
| + visitCall(js.Call node) {
|
| + SourceInformation sourceInformation = node.sourceInformation;
|
| + if (sourceInformation != null) {
|
| + if (node.target is js.PropertyAccess) {
|
| + js.PropertyAccess access = node.target;
|
| + js.Node target = access;
|
| + bool pureAccess = false;
|
| + while (target is js.PropertyAccess) {
|
| + js.PropertyAccess targetAccess = target;
|
| + if (targetAccess.receiver is js.VariableUse ||
|
| + targetAccess.receiver is js.This) {
|
| + pureAccess = true;
|
| + break;
|
| + } else {
|
| + target = targetAccess.receiver;
|
| + }
|
| + }
|
| + if (pureAccess) {
|
| + // a.m() this.m() a.b.c.d.m()
|
| + // ^ ^ ^
|
| + apply(
|
| + node,
|
| + node,
|
| + CodePositionKind.START,
|
| + sourceInformation,
|
| + SourcePositionKind.START);
|
| + } else {
|
| + // *.m() *.a.b.c.d.m()
|
| + // ^ ^
|
| + apply(
|
| + node,
|
| + access.selector,
|
| + CodePositionKind.START,
|
| + sourceInformation,
|
| + SourcePositionKind.CLOSING);
|
| + }
|
| + } else if (node.target is js.VariableUse) {
|
| + // m()
|
| + // ^
|
| + apply(
|
| + node,
|
| + node,
|
| + CodePositionKind.START,
|
| + sourceInformation,
|
| + SourcePositionKind.START);
|
| + } else if (node.target is js.Fun || node.target is js.New) {
|
| + // function(){}() new Function("...")()
|
| + // ^ ^
|
| + apply(
|
| + node,
|
| + node.target,
|
| + CodePositionKind.END,
|
| + sourceInformation,
|
| + SourcePositionKind.CLOSING);
|
| + } else {
|
| + assert(invariant(NO_LOCATION_SPANNABLE, false,
|
| + message: "Unexpected property access ${nodeToString(node)}:\n"
|
| + "${DebugPrinter.prettyPrint(node)}"));
|
| + // Don't know....
|
| + apply(
|
| + node,
|
| + node,
|
| + CodePositionKind.START,
|
| + sourceInformation,
|
| + SourcePositionKind.START);
|
| + }
|
| + }
|
| + visitChildren(node);
|
| + }
|
| +
|
| + @override
|
| + void onPositions(js.Node node,
|
| + int startPosition,
|
| + int endPosition,
|
| + int closingPosition) {
|
| + codePositions.registerPositions(
|
| + node, startPosition, endPosition, closingPosition);
|
| + }
|
| +}
|
|
|