blob: 9b91cab1e6cba46075bb56377e0988984ba6f3ba [file] [log] [blame]
// 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);
}
}