OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// Source information system mapping that attempts a semantic mapping between |
| 6 /// offsets of JavaScript code points to offsets of Dart code points. |
| 7 |
| 8 library dart2js.source_information.position; |
| 9 |
| 10 import '../dart2jslib.dart' show |
| 11 invariant, |
| 12 MessageKind, |
| 13 SourceSpan; |
| 14 import '../elements/elements.dart' show |
| 15 AstElement, |
| 16 LocalElement; |
| 17 import '../js/js.dart' as js; |
| 18 import '../js/js_source_mapping.dart'; |
| 19 import '../js/js_debug.dart'; |
| 20 import '../tree/tree.dart' show Node, Send; |
| 21 import '../util/util.dart' show NO_LOCATION_SPANNABLE; |
| 22 |
| 23 import 'source_file.dart'; |
| 24 import 'source_information.dart'; |
| 25 |
| 26 /// [SourceInformation] that consists of an offset position into the source |
| 27 /// code. |
| 28 class PositionSourceInformation extends SourceInformation { |
| 29 @override |
| 30 final SourceLocation startPosition; |
| 31 |
| 32 @override |
| 33 final SourceLocation closingPosition; |
| 34 |
| 35 PositionSourceInformation(this.startPosition, |
| 36 [this.closingPosition]); |
| 37 |
| 38 @override |
| 39 List<SourceLocation> get sourceLocations { |
| 40 List<SourceLocation> list = <SourceLocation>[]; |
| 41 if (startPosition != null) { |
| 42 list.add(startPosition); |
| 43 } |
| 44 if (closingPosition != null) { |
| 45 list.add(closingPosition); |
| 46 } |
| 47 return list; |
| 48 } |
| 49 |
| 50 @override |
| 51 SourceSpan get sourceSpan { |
| 52 SourceLocation location = |
| 53 startPosition != null ? startPosition : closingPosition; |
| 54 Uri uri = location.sourceUri; |
| 55 int offset = location.offset; |
| 56 return new SourceSpan(uri, offset, offset); |
| 57 } |
| 58 |
| 59 int get hashCode { |
| 60 return 0x7FFFFFFF & |
| 61 (startPosition.hashCode * 17 + closingPosition.hashCode * 19); |
| 62 } |
| 63 |
| 64 bool operator ==(other) { |
| 65 if (identical(this, other)) return true; |
| 66 if (other is! PositionSourceInformation) return false; |
| 67 return startPosition == other.startPosition && |
| 68 closingPosition == other.closingPosition; |
| 69 } |
| 70 |
| 71 /// Create a textual representation of the source information using [uriText] |
| 72 /// as the Uri representation. |
| 73 String _computeText(String uriText) { |
| 74 StringBuffer sb = new StringBuffer(); |
| 75 sb.write('$uriText:'); |
| 76 // Use 1-based line/column info to match usual dart tool output. |
| 77 if (startPosition != null) { |
| 78 sb.write('[${startPosition.line + 1},' |
| 79 '${startPosition.column + 1}]'); |
| 80 } |
| 81 if (closingPosition != null) { |
| 82 sb.write('-[${closingPosition.line + 1},' |
| 83 '${closingPosition.column + 1}]'); |
| 84 } |
| 85 return sb.toString(); |
| 86 } |
| 87 |
| 88 String get shortText { |
| 89 if (startPosition != null) { |
| 90 return _computeText(startPosition.sourceUri.pathSegments.last); |
| 91 } else { |
| 92 return _computeText(closingPosition.sourceUri.pathSegments.last); |
| 93 } |
| 94 } |
| 95 |
| 96 String toString() { |
| 97 if (startPosition != null) { |
| 98 return _computeText('${startPosition.sourceUri}'); |
| 99 } else { |
| 100 return _computeText('${closingPosition.sourceUri}'); |
| 101 } |
| 102 } |
| 103 } |
| 104 |
| 105 class PositionSourceInformationStrategy |
| 106 implements JavaScriptSourceInformationStrategy { |
| 107 const PositionSourceInformationStrategy(); |
| 108 |
| 109 @override |
| 110 SourceInformationBuilder createBuilderForContext(AstElement element) { |
| 111 return new PositionSourceInformationBuilder(element); |
| 112 } |
| 113 |
| 114 @override |
| 115 SourceInformationProcessor createProcessor(SourceMapper mapper) { |
| 116 return new PositionSourceInformationProcessor(mapper); |
| 117 } |
| 118 } |
| 119 |
| 120 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. |
| 121 class PositionSourceInformationBuilder implements SourceInformationBuilder { |
| 122 final SourceFile sourceFile; |
| 123 final String name; |
| 124 |
| 125 PositionSourceInformationBuilder(AstElement element) |
| 126 : sourceFile = element.implementation.compilationUnit.script.file, |
| 127 name = computeElementNameForSourceMaps(element); |
| 128 |
| 129 SourceInformation buildDeclaration(AstElement element) { |
| 130 if (element.isSynthesized) { |
| 131 return new PositionSourceInformation( |
| 132 new OffsetSourceLocation( |
| 133 sourceFile, element.position.charOffset, name)); |
| 134 } else { |
| 135 return new PositionSourceInformation( |
| 136 null, |
| 137 new OffsetSourceLocation(sourceFile, |
| 138 element.resolvedAst.node.getEndToken().charOffset, name)); |
| 139 } |
| 140 } |
| 141 |
| 142 /// Builds a source information object pointing the start position of [node]. |
| 143 SourceInformation buildBegin(Node node) { |
| 144 return new PositionSourceInformation(new OffsetSourceLocation( |
| 145 sourceFile, node.getBeginToken().charOffset, name)); |
| 146 } |
| 147 |
| 148 @override |
| 149 SourceInformation buildGeneric(Node node) => buildBegin(node); |
| 150 |
| 151 @override |
| 152 SourceInformation buildReturn(Node node) => buildBegin(node); |
| 153 |
| 154 @override |
| 155 SourceInformation buildImplicitReturn(AstElement element) { |
| 156 if (element.isSynthesized) { |
| 157 return new PositionSourceInformation( |
| 158 new OffsetSourceLocation( |
| 159 sourceFile, element.position.charOffset, name)); |
| 160 } else { |
| 161 return new PositionSourceInformation( |
| 162 new OffsetSourceLocation(sourceFile, |
| 163 element.resolvedAst.node.getEndToken().charOffset, name)); |
| 164 } |
| 165 } |
| 166 |
| 167 |
| 168 @override |
| 169 SourceInformation buildLoop(Node node) => buildBegin(node); |
| 170 |
| 171 @override |
| 172 SourceInformation buildGet(Node node) => buildBegin(node); |
| 173 |
| 174 @override |
| 175 SourceInformation buildCall(Node receiver, Node call) { |
| 176 return new PositionSourceInformation( |
| 177 new OffsetSourceLocation( |
| 178 sourceFile, receiver.getBeginToken().charOffset, name), |
| 179 new OffsetSourceLocation( |
| 180 sourceFile, call.getBeginToken().charOffset, name)); |
| 181 } |
| 182 |
| 183 @override |
| 184 SourceInformation buildNew(Node node) { |
| 185 return buildBegin(node); |
| 186 } |
| 187 |
| 188 @override |
| 189 SourceInformation buildIf(Node node) => buildBegin(node); |
| 190 |
| 191 @override |
| 192 SourceInformation buildThrow(Node node) => buildBegin(node); |
| 193 |
| 194 @override |
| 195 SourceInformation buildAssignment(Node node) => buildBegin(node); |
| 196 |
| 197 @override |
| 198 SourceInformationBuilder forContext(AstElement element) { |
| 199 return new PositionSourceInformationBuilder(element); |
| 200 } |
| 201 } |
| 202 |
| 203 /// The start, end and closing offsets for a [js.Node]. |
| 204 class CodePosition { |
| 205 final int startPosition; |
| 206 final int endPosition; |
| 207 final int closingPosition; |
| 208 |
| 209 CodePosition(this.startPosition, this.endPosition, this.closingPosition); |
| 210 } |
| 211 |
| 212 /// Registry for mapping [js.Node]s to their [CodePosition]. |
| 213 class CodePositionRecorder { |
| 214 Map<js.Node, CodePosition> _codePositionMap = <js.Node, CodePosition>{}; |
| 215 |
| 216 void registerPositions(js.Node node, |
| 217 int startPosition, |
| 218 int endPosition, |
| 219 int closingPosition) { |
| 220 registerCodePosition(node, |
| 221 new CodePosition(startPosition, endPosition, closingPosition)); |
| 222 } |
| 223 |
| 224 void registerCodePosition(js.Node node, CodePosition codePosition) { |
| 225 _codePositionMap[node] = codePosition; |
| 226 } |
| 227 |
| 228 CodePosition operator [](js.Node node) => _codePositionMap[node]; |
| 229 } |
| 230 |
| 231 enum SourcePositionKind { |
| 232 START, |
| 233 CLOSING, |
| 234 END, |
| 235 } |
| 236 |
| 237 enum CodePositionKind { |
| 238 START, |
| 239 CLOSING, |
| 240 END, |
| 241 } |
| 242 |
| 243 /// Processor that associates [SourceLocation]s from [SourceInformation] on |
| 244 /// [js.Node]s with the target offsets in a [SourceMapper]. |
| 245 class PositionSourceInformationProcessor |
| 246 extends js.BaseVisitor implements SourceInformationProcessor { |
| 247 final CodePositionRecorder codePositions = new CodePositionRecorder(); |
| 248 final SourceMapper sourceMapper; |
| 249 |
| 250 PositionSourceInformationProcessor(this.sourceMapper); |
| 251 |
| 252 void process(js.Node node) { |
| 253 node.accept(this); |
| 254 } |
| 255 |
| 256 void visitChildren(js.Node node) { |
| 257 node.visitChildren(this); |
| 258 } |
| 259 |
| 260 CodePosition getCodePosition(js.Node node) { |
| 261 return codePositions[node]; |
| 262 } |
| 263 |
| 264 /// Associates [sourceInformation] with the JavaScript [node]. |
| 265 /// |
| 266 /// The offset into the JavaScript code is computed by pulling the |
| 267 /// [codePositionKind] from the code positions associated with |
| 268 /// [codePositionNode]. |
| 269 /// |
| 270 /// The mapped Dart source location is computed by pulling the |
| 271 /// [sourcePositionKind] source location from [sourceInformation]. |
| 272 void apply(js.Node node, |
| 273 js.Node codePositionNode, |
| 274 CodePositionKind codePositionKind, |
| 275 SourceInformation sourceInformation, |
| 276 SourcePositionKind sourcePositionKind) { |
| 277 if (sourceInformation != null) { |
| 278 CodePosition codePosition = getCodePosition(codePositionNode); |
| 279 // We should always have recorded the needed code positions. |
| 280 assert(invariant( |
| 281 NO_LOCATION_SPANNABLE, |
| 282 codePosition != null, |
| 283 message: |
| 284 "Code position missing for " |
| 285 "${nodeToString(codePositionNode)}:\n" |
| 286 "${DebugPrinter.prettyPrint(node)}")); |
| 287 if (codePosition == null) return; |
| 288 int codeLocation; |
| 289 SourceLocation sourceLocation; |
| 290 switch (codePositionKind) { |
| 291 case CodePositionKind.START: |
| 292 codeLocation = codePosition.startPosition; |
| 293 break; |
| 294 case CodePositionKind.CLOSING: |
| 295 codeLocation = codePosition.closingPosition; |
| 296 break; |
| 297 case CodePositionKind.END: |
| 298 codeLocation = codePosition.endPosition; |
| 299 break; |
| 300 } |
| 301 switch (sourcePositionKind) { |
| 302 case SourcePositionKind.START: |
| 303 sourceLocation = sourceInformation.startPosition; |
| 304 break; |
| 305 case SourcePositionKind.CLOSING: |
| 306 sourceLocation = sourceInformation.closingPosition; |
| 307 break; |
| 308 case SourcePositionKind.END: |
| 309 sourceLocation = sourceInformation.endPosition; |
| 310 break; |
| 311 } |
| 312 if (codeLocation != null && sourceLocation != null) { |
| 313 sourceMapper.register(node, codeLocation, sourceLocation); |
| 314 } |
| 315 } |
| 316 } |
| 317 |
| 318 @override |
| 319 visitNode(js.Node node) { |
| 320 SourceInformation sourceInformation = node.sourceInformation; |
| 321 if (sourceInformation != null) { |
| 322 /// Associates the left-most position of the JS code with the left-most |
| 323 /// position of the Dart code. |
| 324 apply(node, |
| 325 node, CodePositionKind.START, |
| 326 sourceInformation, SourcePositionKind.START); |
| 327 } |
| 328 visitChildren(node); |
| 329 } |
| 330 |
| 331 @override |
| 332 visitFun(js.Fun node) { |
| 333 SourceInformation sourceInformation = node.sourceInformation; |
| 334 if (sourceInformation != null) { |
| 335 /// Associates the end brace of the JavaScript function with the end brace |
| 336 /// of the Dart function (or the `;` in case of arrow notation). |
| 337 apply(node, |
| 338 node, CodePositionKind.CLOSING, |
| 339 sourceInformation, SourcePositionKind.CLOSING); |
| 340 } |
| 341 |
| 342 visitChildren(node); |
| 343 } |
| 344 |
| 345 @override |
| 346 visitExpressionStatement(js.ExpressionStatement node) { |
| 347 visitChildren(node); |
| 348 } |
| 349 |
| 350 @override |
| 351 visitBinary(js.Binary node) { |
| 352 visitChildren(node); |
| 353 } |
| 354 |
| 355 @override |
| 356 visitAccess(js.PropertyAccess node) { |
| 357 visitChildren(node); |
| 358 } |
| 359 |
| 360 @override |
| 361 visitCall(js.Call node) { |
| 362 SourceInformation sourceInformation = node.sourceInformation; |
| 363 if (sourceInformation != null) { |
| 364 if (node.target is js.PropertyAccess) { |
| 365 js.PropertyAccess access = node.target; |
| 366 js.Node target = access; |
| 367 bool pureAccess = false; |
| 368 while (target is js.PropertyAccess) { |
| 369 js.PropertyAccess targetAccess = target; |
| 370 if (targetAccess.receiver is js.VariableUse || |
| 371 targetAccess.receiver is js.This) { |
| 372 pureAccess = true; |
| 373 break; |
| 374 } else { |
| 375 target = targetAccess.receiver; |
| 376 } |
| 377 } |
| 378 if (pureAccess) { |
| 379 // a.m() this.m() a.b.c.d.m() |
| 380 // ^ ^ ^ |
| 381 apply( |
| 382 node, |
| 383 node, |
| 384 CodePositionKind.START, |
| 385 sourceInformation, |
| 386 SourcePositionKind.START); |
| 387 } else { |
| 388 // *.m() *.a.b.c.d.m() |
| 389 // ^ ^ |
| 390 apply( |
| 391 node, |
| 392 access.selector, |
| 393 CodePositionKind.START, |
| 394 sourceInformation, |
| 395 SourcePositionKind.CLOSING); |
| 396 } |
| 397 } else if (node.target is js.VariableUse) { |
| 398 // m() |
| 399 // ^ |
| 400 apply( |
| 401 node, |
| 402 node, |
| 403 CodePositionKind.START, |
| 404 sourceInformation, |
| 405 SourcePositionKind.START); |
| 406 } else if (node.target is js.Fun || node.target is js.New) { |
| 407 // function(){}() new Function("...")() |
| 408 // ^ ^ |
| 409 apply( |
| 410 node, |
| 411 node.target, |
| 412 CodePositionKind.END, |
| 413 sourceInformation, |
| 414 SourcePositionKind.CLOSING); |
| 415 } else { |
| 416 assert(invariant(NO_LOCATION_SPANNABLE, false, |
| 417 message: "Unexpected property access ${nodeToString(node)}:\n" |
| 418 "${DebugPrinter.prettyPrint(node)}")); |
| 419 // Don't know.... |
| 420 apply( |
| 421 node, |
| 422 node, |
| 423 CodePositionKind.START, |
| 424 sourceInformation, |
| 425 SourcePositionKind.START); |
| 426 } |
| 427 } |
| 428 visitChildren(node); |
| 429 } |
| 430 |
| 431 @override |
| 432 void onPositions(js.Node node, |
| 433 int startPosition, |
| 434 int endPosition, |
| 435 int closingPosition) { |
| 436 codePositions.registerPositions( |
| 437 node, startPosition, endPosition, closingPosition); |
| 438 } |
| 439 } |
OLD | NEW |