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