| // 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 that maps spans of Dart AST nodes to spans of |
| /// JavaScript nodes. |
| |
| library dart2js.source_information.start_end; |
| |
| import '../common.dart'; |
| import '../diagnostics/messages.dart' show |
| MessageTemplate; |
| import '../elements/elements.dart' show |
| AstElement, |
| LocalElement; |
| import '../js/js.dart' as js; |
| import '../js/js_source_mapping.dart'; |
| import '../tokens/token.dart' show Token; |
| import '../tree/tree.dart' show Node, Send; |
| |
| import 'source_file.dart'; |
| import 'source_information.dart'; |
| |
| /// Source information that contains start source position and optionally an |
| /// end source position. |
| class StartEndSourceInformation extends SourceInformation { |
| @override |
| final SourceLocation startPosition; |
| |
| @override |
| final SourceLocation endPosition; |
| |
| StartEndSourceInformation(this.startPosition, [this.endPosition]); |
| |
| @override |
| List<SourceLocation> get sourceLocations { |
| if (endPosition == null) { |
| return <SourceLocation>[startPosition]; |
| } else { |
| return <SourceLocation>[startPosition, endPosition]; |
| } |
| } |
| |
| @override |
| SourceSpan get sourceSpan { |
| Uri uri = startPosition.sourceUri; |
| int begin = startPosition.offset; |
| int end = endPosition == null ? begin : endPosition.offset; |
| return new SourceSpan(uri, begin, end); |
| } |
| |
| int get hashCode { |
| return 0x7FFFFFFF & |
| (startPosition.hashCode * 17 + endPosition.hashCode * 19); |
| } |
| |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is! StartEndSourceInformation) return false; |
| return startPosition == other.startPosition && |
| endPosition == other.endPosition; |
| } |
| |
| // TODO(johnniwinther): Inline this in |
| // [StartEndSourceInformationBuilder.buildDeclaration]. |
| static StartEndSourceInformation _computeSourceInformation( |
| AstElement element) { |
| |
| AstElement implementation = element.implementation; |
| SourceFile sourceFile = implementation.compilationUnit.script.file; |
| String name = computeElementNameForSourceMaps(element); |
| Node node = implementation.node; |
| Token beginToken; |
| Token endToken; |
| if (node == null) { |
| // Synthesized node. Use the enclosing element for the location. |
| beginToken = endToken = element.position; |
| } else { |
| beginToken = node.getBeginToken(); |
| endToken = node.getEndToken(); |
| } |
| // TODO(johnniwinther): find the right sourceFile here and remove offset |
| // checks below. |
| SourceLocation sourcePosition, endSourcePosition; |
| if (beginToken.charOffset < sourceFile.length) { |
| sourcePosition = |
| new OffsetSourceLocation(sourceFile, beginToken.charOffset, name); |
| } |
| if (endToken.charOffset < sourceFile.length) { |
| endSourcePosition = |
| new OffsetSourceLocation(sourceFile, endToken.charOffset, name); |
| } |
| return new StartEndSourceInformation(sourcePosition, endSourcePosition); |
| } |
| |
| /// 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/startPosition info to match usual dart tool output. |
| sb.write('[${startPosition.line + 1},${startPosition.column + 1}]'); |
| if (endPosition != null) { |
| sb.write('-[${endPosition.line + 1},${endPosition.column + 1}]'); |
| } |
| return sb.toString(); |
| } |
| |
| String get shortText { |
| return _computeText(startPosition.sourceUri.pathSegments.last); |
| } |
| |
| String toString() { |
| return _computeText('${startPosition.sourceUri}'); |
| } |
| } |
| |
| class StartEndSourceInformationStrategy |
| implements JavaScriptSourceInformationStrategy { |
| const StartEndSourceInformationStrategy(); |
| |
| @override |
| SourceInformationBuilder createBuilderForContext(AstElement element) { |
| return new StartEndSourceInformationBuilder(element); |
| } |
| |
| @override |
| SourceInformationProcessor createProcessor(SourceMapper sourceMapper) { |
| return new StartEndSourceInformationProcessor(sourceMapper); |
| } |
| } |
| |
| class StartEndSourceInformationProcessor extends SourceInformationProcessor { |
| final SourceMapper sourceMapper; |
| |
| /// Used to track whether a terminating source location marker has been |
| /// registered for the top-most node with source information. |
| bool hasRegisteredRoot = false; |
| |
| StartEndSourceInformationProcessor(this.sourceMapper); |
| |
| @override |
| void onPositions(js.Node node, |
| int startPosition, |
| int endPosition, |
| int closingPosition) { |
| if (node.sourceInformation != null) { |
| StartEndSourceInformation sourceInformation = node.sourceInformation; |
| sourceMapper.register( |
| node, startPosition, sourceInformation.startPosition); |
| if (sourceInformation.endPosition != null) { |
| sourceMapper.register(node, endPosition, sourceInformation.endPosition); |
| } |
| if (!hasRegisteredRoot) { |
| sourceMapper.register(node, endPosition, null); |
| hasRegisteredRoot = true; |
| } |
| } |
| } |
| } |
| |
| /// [SourceInformationBuilder] that generates [PositionSourceInformation]. |
| class StartEndSourceInformationBuilder extends SourceInformationBuilder { |
| final SourceFile sourceFile; |
| final String name; |
| |
| StartEndSourceInformationBuilder(AstElement element) |
| : sourceFile = element.compilationUnit.script.file, |
| name = computeElementNameForSourceMaps(element); |
| |
| SourceInformation buildDeclaration(AstElement element) { |
| return StartEndSourceInformation._computeSourceInformation(element); |
| } |
| |
| SourceLocation sourceFileLocationForToken(Token token) { |
| SourceLocation location = |
| new OffsetSourceLocation(sourceFile, token.charOffset, name); |
| checkValidSourceFileLocation(location, sourceFile, token.charOffset); |
| return location; |
| } |
| |
| void checkValidSourceFileLocation( |
| SourceLocation location, SourceFile sourceFile, int offset) { |
| if (!location.isValid) { |
| throw MessageTemplate.TEMPLATES[MessageKind.INVALID_SOURCE_FILE_LOCATION] |
| .message( |
| {'offset': offset, |
| 'fileName': sourceFile.filename, |
| 'length': sourceFile.length}); |
| } |
| } |
| |
| @override |
| SourceInformation buildLoop(Node node) { |
| return new StartEndSourceInformation( |
| sourceFileLocationForToken(node.getBeginToken()), |
| sourceFileLocationForToken(node.getEndToken())); |
| } |
| |
| @override |
| SourceInformation buildGeneric(Node node) { |
| return new StartEndSourceInformation( |
| sourceFileLocationForToken(node.getBeginToken())); |
| } |
| |
| @override |
| SourceInformation buildCreate(Node node) => buildGeneric(node); |
| |
| @override |
| SourceInformation buildReturn(Node node) => buildGeneric(node); |
| |
| @override |
| SourceInformation buildGet(Node node) => buildGeneric(node); |
| |
| @override |
| SourceInformation buildAssignment(Node node) => buildGeneric(node); |
| |
| @override |
| SourceInformation buildCall(Node receiver, Node call) { |
| return buildGeneric(receiver); |
| } |
| |
| @override |
| SourceInformation buildIf(Node node) => buildGeneric(node); |
| |
| @override |
| SourceInformationBuilder forContext( |
| AstElement element, {SourceInformation sourceInformation}) { |
| return new StartEndSourceInformationBuilder(element); |
| } |
| } |
| |
| |
| |