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