OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 /// Dart classes representing the souce spans and source files. | |
6 library source_maps.span; | |
7 | |
8 import 'dart:math' show min, max; | |
9 | |
10 import 'package:path/path.dart' as p; | |
11 | |
12 import 'src/utils.dart'; | |
13 | |
14 /// A simple class that describe a segment of source text. | |
15 @Deprecated("Use the source_span package instead.") | |
16 abstract class Span implements Comparable { | |
17 /// The start location of this span. | |
18 final Location start; | |
19 | |
20 /// The end location of this span, exclusive. | |
21 final Location end; | |
22 | |
23 /// Url of the source (typically a file) containing this span. | |
24 String get sourceUrl => start.sourceUrl; | |
25 | |
26 /// The length of this span, in characters. | |
27 int get length => end.offset - start.offset; | |
28 | |
29 /// The source text for this span, if available. | |
30 String get text; | |
31 | |
32 /// Whether [text] corresponds to an identifier symbol. | |
33 final bool isIdentifier; | |
34 | |
35 Span(this.start, this.end, bool isIdentifier) | |
36 : isIdentifier = isIdentifier != null ? isIdentifier : false { | |
37 _checkRange(); | |
38 } | |
39 | |
40 /// Creates a new span that is the union of two existing spans [start] and | |
41 /// [end]. Note that the resulting span might contain some positions that were | |
42 /// not in either of the original spans if [start] and [end] are disjoint. | |
43 Span.union(Span start, Span end) | |
44 : start = start.start, end = end.end, isIdentifier = false { | |
45 _checkRange(); | |
46 } | |
47 | |
48 void _checkRange() { | |
49 if (start.offset < 0) throw new ArgumentError('start $start must be >= 0'); | |
50 if (end.offset < start.offset) { | |
51 throw new ArgumentError('end $end must be >= start $start'); | |
52 } | |
53 } | |
54 | |
55 /// Compares two spans. If the spans are not in the same source, this method | |
56 /// generates an error. | |
57 int compareTo(Span other) { | |
58 int d = start.compareTo(other.start); | |
59 return d == 0 ? end.compareTo(other.end) : d; | |
60 } | |
61 | |
62 /// Gets the location in standard printed form `filename:line:column`, where | |
63 /// line and column are adjusted by 1 to match the convention in editors. | |
64 String get formatLocation => start.formatString; | |
65 | |
66 String getLocationMessage(String message, | |
67 {bool useColors: false, String color}) { | |
68 var source = start.sourceUrl == null ? '' : | |
69 ' of ${p.prettyUri(start.sourceUrl)}'; | |
70 return 'line ${start.line + 1}, column ${start.column + 1}$source: ' + | |
71 message; | |
72 } | |
73 | |
74 bool operator ==(Span other) => | |
75 sourceUrl == other.sourceUrl && start == other.start && end == other.end; | |
76 | |
77 int get hashCode => sourceUrl.hashCode + start.offset + (31 * length); | |
78 | |
79 String toString() => '<$runtimeType: $start $end $formatLocation $text>'; | |
80 } | |
81 | |
82 /// A location in the source text | |
83 @Deprecated("Use the source_span package instead.") | |
84 abstract class Location implements Comparable { | |
85 /// Url of the source containing this span. | |
86 String get sourceUrl; | |
87 | |
88 /// The offset of this location, 0-based. | |
89 final int offset; | |
90 | |
91 /// The 0-based line in the source of this location. | |
92 int get line; | |
93 | |
94 /// The 0-based column in the source of this location. | |
95 int get column; | |
96 | |
97 Location(this.offset); | |
98 | |
99 /// Compares two locations. If the locations are not in the same source, this | |
100 /// method generates an error. | |
101 int compareTo(Location other) { | |
102 if (sourceUrl != other.sourceUrl) { | |
103 throw new ArgumentError('can only compare locations of the same source'); | |
104 } | |
105 return offset - other.offset; | |
106 } | |
107 | |
108 bool operator ==(Location other) => | |
109 sourceUrl == other.sourceUrl && offset == other.offset; | |
110 | |
111 int get hashCode => sourceUrl.hashCode + offset; | |
112 | |
113 String toString() => '(Location $offset)'; | |
114 String get formatString => '$sourceUrl:${line + 1}:${column + 1}'; | |
115 } | |
116 | |
117 /// Implementation of [Location] with fixed values given at allocation time. | |
118 @Deprecated("Use the source_span package instead.") | |
119 class FixedLocation extends Location { | |
120 final String sourceUrl; | |
121 final int line; | |
122 final int column; | |
123 | |
124 FixedLocation(int offset, this.sourceUrl, this.line, this.column) | |
125 : super(offset); | |
126 } | |
127 | |
128 /// Implementation of [Span] where all the values are given at allocation time. | |
129 @Deprecated("Use the source_span package instead.") | |
130 class FixedSpan extends Span { | |
131 /// The source text for this span, if available. | |
132 final String text; | |
133 | |
134 /// Creates a span which starts and end in the same line. | |
135 FixedSpan(String sourceUrl, int start, int line, int column, | |
136 {String text: '', bool isIdentifier: false}) | |
137 : text = text, super(new FixedLocation(start, sourceUrl, line, column), | |
138 new FixedLocation(start + text.length, sourceUrl, line, | |
139 column + text.length), | |
140 isIdentifier); | |
141 } | |
142 | |
143 /// [Location] with values computed from an underling [SourceFile]. | |
144 @Deprecated("Use the source_span package instead.") | |
145 class FileLocation extends Location { | |
146 /// The source file containing this location. | |
147 final SourceFile file; | |
148 | |
149 String get sourceUrl => file.url; | |
150 int get line => file.getLine(offset); | |
151 int get column => file.getColumn(line, offset); | |
152 | |
153 FileLocation(this.file, int offset): super(offset); | |
154 } | |
155 | |
156 /// [Span] where values are computed from an underling [SourceFile]. | |
157 @Deprecated("Use the source_span package instead.") | |
158 class FileSpan extends Span { | |
159 /// The source file containing this span. | |
160 final SourceFile file; | |
161 | |
162 /// The source text for this span, if available. | |
163 String get text => file.getText(start.offset, end.offset); | |
164 | |
165 factory FileSpan(SourceFile file, int start, | |
166 [int end, bool isIdentifier = false]) { | |
167 var startLoc = new FileLocation(file, start); | |
168 var endLoc = end == null ? startLoc : new FileLocation(file, end); | |
169 return new FileSpan.locations(startLoc, endLoc, isIdentifier); | |
170 } | |
171 | |
172 FileSpan.locations(FileLocation start, FileLocation end, | |
173 bool isIdentifier) | |
174 : file = start.file, super(start, end, isIdentifier); | |
175 | |
176 /// Creates a new span that is the union of two existing spans [start] and | |
177 /// [end]. Note that the resulting span might contain some positions that were | |
178 /// not in either of the original spans if [start] and [end] are disjoint. | |
179 FileSpan.union(FileSpan start, FileSpan end) | |
180 : file = start.file, super.union(start, end) { | |
181 if (start.file != end.file) { | |
182 throw new ArgumentError('start and end must be from the same file'); | |
183 } | |
184 } | |
185 | |
186 String getLocationMessage(String message, | |
187 {bool useColors: false, String color}) { | |
188 return file.getLocationMessage(message, start.offset, end.offset, | |
189 useColors: useColors, color: color); | |
190 } | |
191 } | |
192 | |
193 // Constants to determine end-of-lines. | |
194 const int _LF = 10; | |
195 const int _CR = 13; | |
196 | |
197 // Color constants used for generating messages. | |
198 const String _RED_COLOR = '\u001b[31m'; | |
199 const String _NO_COLOR = '\u001b[0m'; | |
200 | |
201 /// Stores information about a source file, to permit computation of the line | |
202 /// and column. Also contains a nice default error message highlighting the code | |
203 /// location. | |
204 @Deprecated("Use the source_span package instead.") | |
205 class SourceFile { | |
206 /// Url where the source file is located. | |
207 final String url; | |
208 final List<int> _lineStarts; | |
209 final List<int> _decodedChars; | |
210 | |
211 SourceFile(this.url, this._lineStarts, this._decodedChars); | |
212 | |
213 SourceFile.text(this.url, String text) | |
214 : _lineStarts = <int>[0], | |
215 _decodedChars = text.runes.toList() { | |
216 for (int i = 0; i < _decodedChars.length; i++) { | |
217 var c = _decodedChars[i]; | |
218 if (c == _CR) { | |
219 // Return not followed by newline is treated as a newline | |
220 int j = i + 1; | |
221 if (j >= _decodedChars.length || _decodedChars[j] != _LF) { | |
222 c = _LF; | |
223 } | |
224 } | |
225 if (c == _LF) _lineStarts.add(i + 1); | |
226 } | |
227 } | |
228 | |
229 /// Returns a span in this [SourceFile] with the given offsets. | |
230 Span span(int start, [int end, bool isIdentifier = false]) => | |
231 new FileSpan(this, start, end, isIdentifier); | |
232 | |
233 /// Returns a location in this [SourceFile] with the given offset. | |
234 Location location(int offset) => new FileLocation(this, offset); | |
235 | |
236 /// Gets the 0-based line corresponding to an offset. | |
237 int getLine(int offset) => binarySearch(_lineStarts, (o) => o > offset) - 1; | |
238 | |
239 /// Gets the 0-based column corresponding to an offset. | |
240 int getColumn(int line, int offset) { | |
241 if (line < 0 || line >= _lineStarts.length) return 0; | |
242 return offset - _lineStarts[line]; | |
243 } | |
244 | |
245 /// Get the offset for a given line and column | |
246 int getOffset(int line, int column) { | |
247 if (line < 0) return getOffset(0, 0); | |
248 if (line < _lineStarts.length) { | |
249 return _lineStarts[line] + column; | |
250 } else { | |
251 return _decodedChars.length; | |
252 } | |
253 } | |
254 | |
255 /// Gets the text at the given offsets. | |
256 String getText(int start, [int end]) => | |
257 new String.fromCharCodes(_decodedChars.sublist(max(start, 0), end)); | |
258 | |
259 /// Create a pretty string representation from a span. | |
260 String getLocationMessage(String message, int start, int end, | |
261 {bool useColors: false, String color}) { | |
262 // TODO(jmesserly): it would be more useful to pass in an object that | |
263 // controls how the errors are printed. This method is a bit too smart. | |
264 var line = getLine(start); | |
265 var column = getColumn(line, start); | |
266 | |
267 var source = url == null ? '' : ' of ${p.prettyUri(url)}'; | |
268 var msg = 'line ${line + 1}, column ${column + 1}$source: $message'; | |
269 | |
270 if (_decodedChars == null) { | |
271 // We don't have any text to include, so exit. | |
272 return msg; | |
273 } | |
274 | |
275 var buf = new StringBuffer(msg); | |
276 buf.write('\n'); | |
277 | |
278 // +1 for 0-indexing, +1 again to avoid the last line | |
279 var textLine = getText(getOffset(line, 0), getOffset(line + 1, 0)); | |
280 | |
281 column = min(column, textLine.length - 1); | |
282 int toColumn = min(column + end - start, textLine.length); | |
283 if (useColors) { | |
284 if (color == null) { | |
285 color = _RED_COLOR; | |
286 } | |
287 buf.write(textLine.substring(0, column)); | |
288 buf.write(color); | |
289 buf.write(textLine.substring(column, toColumn)); | |
290 buf.write(_NO_COLOR); | |
291 buf.write(textLine.substring(toColumn)); | |
292 } else { | |
293 buf.write(textLine); | |
294 if (textLine != '' && !textLine.endsWith('\n')) buf.write('\n'); | |
295 } | |
296 | |
297 int i = 0; | |
298 for (; i < column; i++) { | |
299 buf.write(' '); | |
300 } | |
301 | |
302 if (useColors) buf.write(color); | |
303 for (; i < toColumn; i++) { | |
304 buf.write('^'); | |
305 } | |
306 if (useColors) buf.write(_NO_COLOR); | |
307 return buf.toString(); | |
308 } | |
309 } | |
310 | |
311 /// A convenience type to treat a code segment as if it were a separate | |
312 /// [SourceFile]. A [SourceFileSegment] shifts all locations by an offset, which | |
313 /// allows you to set source-map locations based on the locations relative to | |
314 /// the start of the segment, but that get translated to absolute locations in | |
315 /// the original source file. | |
316 @Deprecated("Use the source_span package instead.") | |
317 class SourceFileSegment extends SourceFile { | |
318 final int _baseOffset; | |
319 final int _baseLine; | |
320 final int _baseColumn; | |
321 final int _maxOffset; | |
322 | |
323 SourceFileSegment(String url, String textSegment, Location startOffset) | |
324 : _baseOffset = startOffset.offset, | |
325 _baseLine = startOffset.line, | |
326 _baseColumn = startOffset.column, | |
327 _maxOffset = startOffset.offset + textSegment.length, | |
328 super.text(url, textSegment); | |
329 | |
330 /// Craete a span, where [start] is relative to this segment's base offset. | |
331 /// The returned span stores the real offset on the file, so that error | |
332 /// messages are reported at the real location. | |
333 Span span(int start, [int end, bool isIdentifier = false]) => | |
334 super.span(start + _baseOffset, | |
335 end == null ? null : end + _baseOffset, isIdentifier); | |
336 | |
337 /// Create a location, where [offset] relative to this segment's base offset. | |
338 /// The returned span stores the real offset on the file, so that error | |
339 /// messages are reported at the real location. | |
340 Location location(int offset) => super.location(offset + _baseOffset); | |
341 | |
342 /// Return the line on the underlying file associated with the [offset] of the | |
343 /// underlying file. This method operates on the real offsets from the | |
344 /// original file, so that error messages can be reported accurately. When the | |
345 /// requested offset is past the length of the segment, this returns the line | |
346 /// number after the end of the segment (total lines + 1). | |
347 int getLine(int offset) { | |
348 var res = super.getLine(max(offset - _baseOffset, 0)) + _baseLine; | |
349 return (offset > _maxOffset) ? res + 1 : res; | |
350 } | |
351 | |
352 /// Return the column on the underlying file associated with [line] and | |
353 /// [offset], where [line] is absolute from the beginning of the underlying | |
354 /// file. This method operates on the real offsets from the original file, so | |
355 /// that error messages can be reported accurately. | |
356 int getColumn(int line, int offset) { | |
357 var col = super.getColumn(line - _baseLine, max(offset - _baseOffset, 0)); | |
358 return line == _baseLine ? col + _baseColumn : col; | |
359 } | |
360 | |
361 /// Return the offset associated with a line and column. This method operates | |
362 /// on the real offsets from the original file, so that error messages can be | |
363 /// reported accurately. | |
364 int getOffset(int line, int column) => | |
365 super.getOffset(line - _baseLine, | |
366 line == _baseLine ? column - _baseColumn : column) + _baseOffset; | |
367 | |
368 /// Retrieve the text associated with the specified range. This method | |
369 /// operates on the real offsets from the original file, so that error | |
370 /// messages can be reported accurately. | |
371 String getText(int start, [int end]) => | |
372 super.getText(start - _baseOffset, end == null ? null : end - _baseOffset); | |
373 } | |
374 | |
375 /// A class for exceptions that have source span information attached. | |
376 @Deprecated("Use the source_span package instead.") | |
377 class SpanException implements Exception { | |
378 /// A message describing the exception. | |
379 final String message; | |
380 | |
381 /// The span associated with this exception. | |
382 /// | |
383 /// This may be `null` if the source location can't be determined. | |
384 final Span span; | |
385 | |
386 SpanException(this.message, this.span); | |
387 | |
388 String toString({bool useColors: false, String color}) { | |
389 if (span == null) return message; | |
390 return "Error on " + span.getLocationMessage(message, | |
391 useColors: useColors, color: color); | |
392 } | |
393 } | |
394 | |
395 /// A [SpanException] that's also a [FormatException]. | |
396 @Deprecated("Use the source_span package instead.") | |
397 class SpanFormatException extends SpanException implements FormatException { | |
398 final source; | |
399 | |
400 SpanFormatException(String message, Span span, [this.source]) | |
401 : super(message, span); | |
402 | |
403 int get offset => span == null ? null : span.start.offset; | |
404 } | |
OLD | NEW |