Chromium Code Reviews| 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 String get shortText { | |
| 72 StringBuffer sb = new StringBuffer(); | |
| 73 if (startPosition != null) { | |
| 74 sb.write('${startPosition.sourceUri.pathSegments.last}:'); | |
| 75 } else { | |
| 76 sb.write('${closingPosition.sourceUri.pathSegments.last}:'); | |
| 77 } | |
| 78 // Use 1-based line/column info to match usual dart tool output. | |
| 79 if (startPosition != null) { | |
| 80 sb.write('[${startPosition.line + 1},' | |
| 81 '${startPosition.column + 1}]'); | |
| 82 } | |
| 83 if (closingPosition != null) { | |
| 84 sb.write('-[${closingPosition.line + 1},' | |
| 85 '${closingPosition.column + 1}]'); | |
| 86 } | |
| 87 return sb.toString(); | |
| 88 } | |
| 89 | |
| 90 String toString() { | |
| 91 StringBuffer sb = new StringBuffer(); | |
| 92 if (startPosition != null) { | |
| 93 sb.write('${startPosition.sourceUri}:'); | |
| 94 } else { | |
| 95 sb.write('${closingPosition.sourceUri}:'); | |
| 96 } | |
| 97 // Use 1-based line/column info to match usual dart tool output. | |
| 98 if (startPosition != null) { | |
| 99 sb.write('[${startPosition.line + 1},' | |
| 100 '${startPosition.column + 1}]'); | |
| 101 } | |
| 102 if (closingPosition != null) { | |
| 103 sb.write('-[${closingPosition.line + 1},' | |
| 104 '${closingPosition.column + 1}]'); | |
| 105 } | |
| 106 return sb.toString(); | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 class PositionSourceInformationStrategy | |
| 111 implements JavaScriptSourceInformationStrategy { | |
| 112 const PositionSourceInformationStrategy(); | |
| 113 | |
| 114 @override | |
| 115 SourceInformationBuilder createBuilderForContext(AstElement element) { | |
| 116 return new PositionSourceInformationBuilder(element); | |
| 117 } | |
| 118 | |
| 119 @override | |
| 120 SourceInformationProcessor createProcessor(SourceMapper mapper) { | |
| 121 return new PositionSourceInformationProcessor(mapper); | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. | |
| 126 class PositionSourceInformationBuilder implements SourceInformationBuilder { | |
| 127 final SourceFile sourceFile; | |
| 128 final String name; | |
| 129 | |
| 130 PositionSourceInformationBuilder(AstElement element) | |
| 131 : sourceFile = element.implementation.compilationUnit.script.file, | |
| 132 name = computeElementNameForSourceMaps(element); | |
| 133 | |
| 134 SourceInformation buildDeclaration(AstElement element) { | |
| 135 if (element.isSynthesized) { | |
| 136 return new PositionSourceInformation( | |
| 137 new OffsetSourceLocation( | |
| 138 sourceFile, element.position.charOffset, name)); | |
| 139 } else { | |
| 140 return new PositionSourceInformation( | |
| 141 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
| |
| 142 new OffsetSourceLocation(sourceFile, | |
| 143 element.resolvedAst.node.getEndToken().charOffset, name)); | |
| 144 } | |
| 145 } | |
| 146 | |
| 147 SourceInformation buildBegin(Node node) { | |
|
floitsch
2015/06/29 08:55:51
add documentation.
Johnni Winther
2015/06/29 12:36:28
Done.
| |
| 148 return new PositionSourceInformation(new OffsetSourceLocation( | |
| 149 sourceFile, node.getBeginToken().charOffset, name)); | |
| 150 } | |
| 151 | |
| 152 @override | |
| 153 SourceInformation buildGeneric(Node node) => buildBegin(node); | |
| 154 | |
| 155 @override | |
| 156 SourceInformation buildReturn(Node node) => buildBegin(node); | |
| 157 | |
| 158 @override | |
| 159 SourceInformation buildImplicitReturn(AstElement element) { | |
| 160 if (element.isSynthesized) { | |
| 161 return new PositionSourceInformation( | |
| 162 new OffsetSourceLocation( | |
| 163 sourceFile, element.position.charOffset, name)); | |
| 164 } else { | |
| 165 return new PositionSourceInformation( | |
| 166 new OffsetSourceLocation(sourceFile, | |
| 167 element.resolvedAst.node.getEndToken().charOffset, name)); | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 | |
| 172 @override | |
| 173 SourceInformation buildLoop(Node node) => buildBegin(node); | |
| 174 | |
| 175 @override | |
| 176 SourceInformation buildGet(Node node) => buildBegin(node); | |
| 177 | |
| 178 @override | |
| 179 SourceInformation buildCall(Node receiver, Node call) { | |
| 180 return new PositionSourceInformation( | |
| 181 new OffsetSourceLocation( | |
| 182 sourceFile, receiver.getBeginToken().charOffset, name), | |
| 183 new OffsetSourceLocation( | |
| 184 sourceFile, call.getBeginToken().charOffset, name)); | |
| 185 } | |
| 186 | |
| 187 @override | |
| 188 SourceInformation buildNew(Node node) { | |
| 189 return buildBegin(node); | |
| 190 } | |
| 191 | |
| 192 @override | |
| 193 SourceInformation buildIf(Node node) => buildBegin(node); | |
| 194 | |
| 195 @override | |
| 196 SourceInformation buildThrow(Node node) => buildBegin(node); | |
| 197 | |
| 198 @override | |
| 199 SourceInformation buildAssignment(Node node) => buildBegin(node); | |
| 200 | |
| 201 @override | |
| 202 SourceInformationBuilder forContext(AstElement element) { | |
| 203 return new PositionSourceInformationBuilder(element); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 /// The start, end and closing offsets for a [js.Node]. | |
| 208 class CodePosition { | |
| 209 final int startPosition; | |
| 210 final int endPosition; | |
| 211 final int closingPosition; | |
| 212 | |
| 213 CodePosition(this.startPosition, this.endPosition, this.closingPosition); | |
| 214 } | |
| 215 | |
| 216 /// Registry for mapping [js.Node]s to their [CodePosition]. | |
| 217 class CodePositionRecorder { | |
| 218 Map<js.Node, CodePosition> _codePositionMap = <js.Node, CodePosition>{}; | |
| 219 | |
| 220 void registerPositions(js.Node node, | |
| 221 int startPosition, | |
| 222 int endPosition, | |
| 223 int closingPosition) { | |
| 224 registerCodePosition(node, | |
| 225 new CodePosition(startPosition, endPosition, closingPosition)); | |
| 226 } | |
| 227 | |
| 228 void registerCodePosition(js.Node node, CodePosition codePosition) { | |
| 229 _codePositionMap[node] = codePosition; | |
| 230 } | |
| 231 | |
| 232 CodePosition operator [](js.Node node) => _codePositionMap[node]; | |
| 233 } | |
| 234 | |
| 235 enum SourcePositionKind { | |
| 236 START, | |
| 237 CLOSING, | |
| 238 END, | |
| 239 } | |
| 240 | |
| 241 enum CodePositionKind { | |
| 242 START, | |
| 243 CLOSING, | |
| 244 END, | |
| 245 } | |
| 246 | |
| 247 /// Processor that associates [SourceLocation]s from [SourceInformation] on | |
| 248 /// [js.Node]s with the target offsets in a [SourceMapper]. | |
| 249 class PositionSourceInformationProcessor | |
| 250 extends js.BaseVisitor implements SourceInformationProcessor { | |
| 251 final CodePositionRecorder codePositions = new CodePositionRecorder(); | |
| 252 final SourceMapper sourceMapper; | |
| 253 | |
| 254 PositionSourceInformationProcessor(this.sourceMapper); | |
| 255 | |
| 256 void process(js.Node node) { | |
| 257 node.accept(this); | |
| 258 } | |
| 259 | |
| 260 void visitChildren(js.Node node) { | |
| 261 node.visitChildren(this); | |
| 262 } | |
| 263 | |
| 264 CodePosition getCodePosition(js.Node node) { | |
| 265 return codePositions[node]; | |
| 266 } | |
| 267 | |
| 268 /// Associates [sourceInformation] with [codePosition] for [node] using | |
| 269 /// [codePositionKind] and [sourcePositionKind] to pick the offsets and | |
| 270 /// source locations. | |
| 271 void apply(js.Node node, | |
| 272 CodePosition codePosition, | |
| 273 CodePositionKind codePositionKind, | |
| 274 SourceInformation sourceInformation, | |
| 275 SourcePositionKind sourcePositionKind) { | |
| 276 if (sourceInformation != null) { | |
| 277 // We should always have recorded the needed code positions. | |
| 278 assert(codePosition != null); | |
| 279 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
| |
| 280 int codeLocation; | |
| 281 SourceLocation sourceLocation; | |
| 282 switch (codePositionKind) { | |
| 283 case CodePositionKind.START: | |
| 284 codeLocation = codePosition.startPosition; | |
| 285 break; | |
| 286 case CodePositionKind.CLOSING: | |
| 287 codeLocation = codePosition.closingPosition; | |
| 288 break; | |
| 289 case CodePositionKind.END: | |
| 290 codeLocation = codePosition.endPosition; | |
| 291 break; | |
| 292 } | |
| 293 switch (sourcePositionKind) { | |
| 294 case SourcePositionKind.START: | |
| 295 sourceLocation = sourceInformation.startPosition; | |
| 296 break; | |
| 297 case SourcePositionKind.CLOSING: | |
| 298 sourceLocation = sourceInformation.closingPosition; | |
| 299 break; | |
| 300 case SourcePositionKind.END: | |
| 301 sourceLocation = sourceInformation.endPosition; | |
| 302 break; | |
| 303 } | |
| 304 if (codeLocation != null && sourceLocation != null) { | |
| 305 sourceMapper.register(node, codeLocation, sourceLocation); | |
| 306 } | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 @override | |
| 311 visitNode(js.Node node) { | |
| 312 SourceInformation sourceInformation = node.sourceInformation; | |
| 313 if (sourceInformation != null) { | |
| 314 apply(node, | |
| 315 getCodePosition(node), CodePositionKind.START, | |
|
floitsch
2015/06/29 08:55:51
Add comments when "start" and when "close" is used
| |
| 316 sourceInformation, SourcePositionKind.START); | |
| 317 } | |
| 318 visitChildren(node); | |
| 319 } | |
| 320 | |
| 321 @override | |
| 322 visitFun(js.Fun node) { | |
| 323 SourceInformation sourceInformation = node.sourceInformation; | |
| 324 if (sourceInformation != null) { | |
| 325 apply(node, | |
| 326 getCodePosition(node), CodePositionKind.CLOSING, | |
| 327 sourceInformation, SourcePositionKind.CLOSING); | |
| 328 } | |
| 329 | |
| 330 visitChildren(node); | |
| 331 } | |
| 332 | |
| 333 @override | |
| 334 visitExpressionStatement(js.ExpressionStatement node) { | |
| 335 visitChildren(node); | |
| 336 } | |
| 337 | |
| 338 @override | |
| 339 visitBinary(js.Binary node) { | |
| 340 visitChildren(node); | |
| 341 } | |
| 342 | |
| 343 @override | |
| 344 visitAccess(js.PropertyAccess node) { | |
| 345 visitChildren(node); | |
| 346 } | |
| 347 | |
| 348 @override | |
| 349 visitCall(js.Call node) { | |
| 350 const bool LOG = false; | |
| 351 SourceInformation sourceInformation = node.sourceInformation; | |
| 352 if (sourceInformation != null) { | |
| 353 if (node.target is js.PropertyAccess) { | |
| 354 js.PropertyAccess access = node.target; | |
| 355 js.Node target = access; | |
| 356 bool pureAccess = false; | |
| 357 while (target is js.PropertyAccess) { | |
| 358 js.PropertyAccess targetAccess = target; | |
| 359 if (targetAccess.receiver is js.VariableUse || | |
| 360 targetAccess.receiver is js.This) { | |
| 361 pureAccess = true; | |
| 362 break; | |
| 363 } else { | |
| 364 target = targetAccess.receiver; | |
| 365 } | |
| 366 } | |
| 367 if (pureAccess) { | |
| 368 // a.m() this.m() a.b.c.d.m() | |
| 369 // ^ ^ ^ | |
| 370 apply( | |
| 371 node, | |
| 372 getCodePosition(node), | |
| 373 CodePositionKind.START, | |
| 374 sourceInformation, | |
| 375 SourcePositionKind.START); | |
| 376 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.
| |
| 377 print('case 1: `${nodeToString(node)}`'); | |
| 378 print(DebugPrinter.prettyPrint(node)); | |
| 379 } | |
| 380 } else { | |
| 381 // *.m() *.a.b.c.d.m() | |
| 382 // ^ ^ | |
| 383 apply( | |
| 384 node, | |
| 385 getCodePosition(access.selector), | |
| 386 CodePositionKind.START, | |
| 387 sourceInformation, | |
| 388 SourcePositionKind.CLOSING); | |
| 389 } | |
| 390 } else if (node.target is js.VariableUse) { | |
| 391 // m() | |
| 392 // ^ | |
| 393 apply( | |
| 394 node, | |
| 395 getCodePosition(node), | |
| 396 CodePositionKind.START, | |
| 397 sourceInformation, | |
| 398 SourcePositionKind.START); | |
| 399 } else if (node.target is js.Fun || node.target is js.New) { | |
| 400 // function(){}() new Function("...")() | |
| 401 // ^ ^ | |
| 402 apply( | |
| 403 node, | |
| 404 getCodePosition(node.target), | |
| 405 CodePositionKind.END, | |
| 406 sourceInformation, | |
| 407 SourcePositionKind.CLOSING); | |
| 408 } else { | |
| 409 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
| 410 message: "Unexpected property access ${nodeToString(node)}:\n" | |
| 411 "${DebugPrinter.prettyPrint(node)}")); | |
| 412 // 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 (
| |
| 413 apply( | |
| 414 node, | |
| 415 getCodePosition(node), | |
| 416 CodePositionKind.START, | |
| 417 sourceInformation, | |
| 418 SourcePositionKind.START); | |
| 419 } | |
| 420 } | |
| 421 visitChildren(node); | |
| 422 } | |
| 423 | |
| 424 @override | |
| 425 void onPositions(js.Node node, | |
| 426 int startPosition, | |
| 427 int endPosition, | |
| 428 int closingPosition) { | |
| 429 codePositions.registerPositions( | |
| 430 node, startPosition, endPosition, closingPosition); | |
|
floitsch
2015/06/29 08:55:52
extra space.
Johnni Winther
2015/06/29 12:36:29
Done.
| |
| 431 } | |
| 432 } | |
| OLD | NEW |