| 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 |