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); |
+ } |
+} |