Chromium Code Reviews| 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..e6fd3a0a2b605f4c8d3a11ea049b1ba8a7968730 |
| --- /dev/null |
| +++ b/pkg/compiler/lib/src/io/position_information.dart |
| @@ -0,0 +1,432 @@ |
| +// 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; |
| + } |
| + |
| + String get shortText { |
| + StringBuffer sb = new StringBuffer(); |
| + if (startPosition != null) { |
| + sb.write('${startPosition.sourceUri.pathSegments.last}:'); |
| + } else { |
| + sb.write('${closingPosition.sourceUri.pathSegments.last}:'); |
| + } |
| + // 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 toString() { |
| + StringBuffer sb = new StringBuffer(); |
| + if (startPosition != null) { |
| + sb.write('${startPosition.sourceUri}:'); |
| + } else { |
| + sb.write('${closingPosition.sourceUri}:'); |
| + } |
| + // 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(); |
| + } |
| +} |
| + |
| +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, |
|
floitsch
2015/06/29 08:55:51
why does this not have a start-position?
Johnni Winther
2015/06/29 12:36:29
The start of a function (the signature) is not a s
|
| + new OffsetSourceLocation(sourceFile, |
| + element.resolvedAst.node.getEndToken().charOffset, name)); |
| + } |
| + } |
| + |
| + SourceInformation buildBegin(Node node) { |
|
floitsch
2015/06/29 08:55:51
add documentation.
Johnni Winther
2015/06/29 12:36:28
Done.
|
| + 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 [codePosition] for [node] using |
| + /// [codePositionKind] and [sourcePositionKind] to pick the offsets and |
| + /// source locations. |
| + void apply(js.Node node, |
| + CodePosition codePosition, |
| + CodePositionKind codePositionKind, |
| + SourceInformation sourceInformation, |
| + SourcePositionKind sourcePositionKind) { |
| + if (sourceInformation != null) { |
| + // We should always have recorded the needed code positions. |
| + assert(codePosition != null); |
| + if (codePosition == null) return; |
|
floitsch
2015/06/29 08:55:51
that sounds wrong: first an assert, but then a tes
Johnni Winther
2015/06/29 12:36:29
It _should_ be the case that we have points for th
|
| + 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) { |
| + apply(node, |
| + getCodePosition(node), CodePositionKind.START, |
|
floitsch
2015/06/29 08:55:51
Add comments when "start" and when "close" is used
|
| + sourceInformation, SourcePositionKind.START); |
| + } |
| + visitChildren(node); |
| + } |
| + |
| + @override |
| + visitFun(js.Fun node) { |
| + SourceInformation sourceInformation = node.sourceInformation; |
| + if (sourceInformation != null) { |
| + apply(node, |
| + getCodePosition(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) { |
| + const bool LOG = false; |
| + 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, |
| + getCodePosition(node), |
| + CodePositionKind.START, |
| + sourceInformation, |
| + SourcePositionKind.START); |
| + if (LOG) { |
|
floitsch
2015/06/29 08:55:52
I would remove this and the const bool.
Johnni Winther
2015/06/29 12:36:29
Done.
|
| + print('case 1: `${nodeToString(node)}`'); |
| + print(DebugPrinter.prettyPrint(node)); |
| + } |
| + } else { |
| + // *.m() *.a.b.c.d.m() |
| + // ^ ^ |
| + apply( |
| + node, |
| + getCodePosition(access.selector), |
| + CodePositionKind.START, |
| + sourceInformation, |
| + SourcePositionKind.CLOSING); |
| + } |
| + } else if (node.target is js.VariableUse) { |
| + // m() |
| + // ^ |
| + apply( |
| + node, |
| + getCodePosition(node), |
| + CodePositionKind.START, |
| + sourceInformation, |
| + SourcePositionKind.START); |
| + } else if (node.target is js.Fun || node.target is js.New) { |
| + // function(){}() new Function("...")() |
| + // ^ ^ |
| + apply( |
| + node, |
| + getCodePosition(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.... |
|
floitsch
2015/06/29 08:55:51
Given that there is an assert, we should throw an
Johnni Winther
2015/06/29 12:36:29
I don't want compilation to fail because of this (
|
| + apply( |
| + node, |
| + getCodePosition(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); |
|
floitsch
2015/06/29 08:55:52
extra space.
Johnni Winther
2015/06/29 12:36:29
Done.
|
| + } |
| +} |