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 |