OLD | NEW |
---|---|
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 library source_span.file; | 5 library source_span.file; |
6 | 6 |
7 import 'dart:math' as math; | 7 import 'dart:math' as math; |
8 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
9 | 9 |
10 import 'package:path/path.dart' as p; | |
11 | |
12 import 'colors.dart' as colors; | |
13 import 'location.dart'; | 10 import 'location.dart'; |
14 import 'span.dart'; | 11 import 'span.dart'; |
15 import 'span_mixin.dart'; | 12 import 'span_mixin.dart'; |
16 import 'span_with_context.dart'; | 13 import 'span_with_context.dart'; |
17 import 'utils.dart'; | 14 import 'utils.dart'; |
18 | 15 |
19 // Constants to determine end-of-lines. | 16 // Constants to determine end-of-lines. |
20 const int _LF = 10; | 17 const int _LF = 10; |
21 const int _CR = 13; | 18 const int _CR = 13; |
22 | 19 |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
67 } | 64 } |
68 if (c == _LF) _lineStarts.add(i + 1); | 65 if (c == _LF) _lineStarts.add(i + 1); |
69 } | 66 } |
70 } | 67 } |
71 | 68 |
72 /// Returns a span in [this] from [start] to [end] (exclusive). | 69 /// Returns a span in [this] from [start] to [end] (exclusive). |
73 /// | 70 /// |
74 /// If [end] isn't passed, it defaults to the end of the file. | 71 /// If [end] isn't passed, it defaults to the end of the file. |
75 FileSpan span(int start, [int end]) { | 72 FileSpan span(int start, [int end]) { |
76 if (end == null) end = length - 1; | 73 if (end == null) end = length - 1; |
77 return new FileSpan._(this, start, end); | 74 return new _FileSpan(this, start, end); |
78 } | 75 } |
79 | 76 |
80 /// Returns a location in [this] at [offset]. | 77 /// Returns a location in [this] at [offset]. |
81 FileLocation location(int offset) => new FileLocation._(this, offset); | 78 FileLocation location(int offset) => new FileLocation._(this, offset); |
82 | 79 |
83 /// Gets the 0-based line corresponding to [offset]. | 80 /// Gets the 0-based line corresponding to [offset]. |
84 int getLine(int offset) { | 81 int getLine(int offset) { |
85 if (offset < 0) { | 82 if (offset < 0) { |
86 throw new RangeError("Offset may not be negative, was $offset."); | 83 throw new RangeError("Offset may not be negative, was $offset."); |
87 } else if (offset > length) { | 84 } else if (offset > length) { |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
166 int get column => file.getColumn(offset); | 163 int get column => file.getColumn(offset); |
167 | 164 |
168 FileLocation._(this.file, int offset) | 165 FileLocation._(this.file, int offset) |
169 : super(offset) { | 166 : super(offset) { |
170 if (offset > file.length) { | 167 if (offset > file.length) { |
171 throw new RangeError("Offset $offset must not be greater than the number " | 168 throw new RangeError("Offset $offset must not be greater than the number " |
172 "of characters in the file, ${file.length}."); | 169 "of characters in the file, ${file.length}."); |
173 } | 170 } |
174 } | 171 } |
175 | 172 |
176 FileSpan pointSpan() => new FileSpan._(file, offset, offset); | 173 FileSpan pointSpan() => new _FileSpan(file, offset, offset); |
177 } | 174 } |
178 | 175 |
179 /// A [SourceSpan] within a [SourceFile]. | 176 /// A [SourceSpan] within a [SourceFile]. |
180 /// | 177 /// |
181 /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column | 178 /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column |
182 /// values based on its offset and the contents of [file]. [FileSpan.message] is | 179 /// values based on its offset and the contents of [file]. [FileSpan.message] is |
183 /// also able to provide more context then [SourceSpan.message], and | 180 /// also able to provide more context then [SourceSpan.message], and |
184 /// [FileSpan.union] will return a [FileSpan] if possible. | 181 /// [FileSpan.union] will return a [FileSpan] if possible. |
185 /// | 182 /// |
186 /// A [FileSpan] can be created using [SourceFile.span]. | 183 /// A [FileSpan] can be created using [SourceFile.span]. |
187 class FileSpan extends SourceSpanMixin implements SourceSpanWithContext { | 184 abstract class FileSpan implements SourceSpanWithContext { |
188 /// The [file] that [this] belongs to. | 185 /// The [file] that [this] belongs to. |
186 SourceFile get file; | |
187 | |
188 /// Returns a new span that covers both [this] and [other]. | |
189 /// | |
190 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text | |
191 /// between the two will be covered by the returned span. | |
192 FileSpan expand(FileSpan other); | |
193 } | |
194 | |
195 /// The implementation of [FileSpan]. | |
196 /// | |
197 /// This is split into a separate class so that `is _FileSpan` checks can be run | |
198 /// to make certain operations more efficient. If we used `is FileSpan`, that | |
199 /// would break if external classes implemented the interface. | |
200 class _FileSpan extends SourceSpanMixin implements FileSpan { | |
189 final SourceFile file; | 201 final SourceFile file; |
190 | 202 |
191 /// The offset of the beginning of the span. | 203 /// The offset of the beginning of the span. |
192 /// | 204 /// |
193 /// [start] is lazily generated from this to avoid allocating unnecessary | 205 /// [start] is lazily generated from this to avoid allocating unnecessary |
194 /// objects. | 206 /// objects. |
195 final int _start; | 207 final int _start; |
196 | 208 |
197 /// The offset of the end of the span. | 209 /// The offset of the end of the span. |
198 /// | 210 /// |
199 /// [end] is lazily generated from this to avoid allocating unnecessary | 211 /// [end] is lazily generated from this to avoid allocating unnecessary |
200 /// objects. | 212 /// objects. |
201 final int _end; | 213 final int _end; |
202 | 214 |
203 Uri get sourceUrl => file.url; | 215 Uri get sourceUrl => file.url; |
204 int get length => _end - _start; | 216 int get length => _end - _start; |
205 FileLocation get start => new FileLocation._(file, _start); | 217 FileLocation get start => new FileLocation._(file, _start); |
206 FileLocation get end => new FileLocation._(file, _end); | 218 FileLocation get end => new FileLocation._(file, _end); |
207 String get text => file.getText(_start, _end); | 219 String get text => file.getText(_start, _end); |
208 String get context => file.getText(file.getOffset(start.line), | 220 String get context => file.getText(file.getOffset(start.line), |
209 end.line == file.lines - 1 ? null : file.getOffset(end.line + 1)); | 221 end.line == file.lines - 1 ? null : file.getOffset(end.line + 1)); |
210 | 222 |
211 FileSpan._(this.file, this._start, this._end) { | 223 _FileSpan(this.file, this._start, this._end) { |
212 if (_end < _start) { | 224 if (_end < _start) { |
213 throw new ArgumentError('End $_end must come after start $_start.'); | 225 throw new ArgumentError('End $_end must come after start $_start.'); |
214 } else if (_end > file.length) { | 226 } else if (_end > file.length) { |
215 throw new RangeError("End $_end must not be greater than the number " | 227 throw new RangeError("End $_end must not be greater than the number " |
216 "of characters in the file, ${file.length}."); | 228 "of characters in the file, ${file.length}."); |
217 } else if (_start < 0) { | 229 } else if (_start < 0) { |
218 throw new RangeError("Start may not be negative, was $_start."); | 230 throw new RangeError("Start may not be negative, was $_start."); |
219 } | 231 } |
220 } | 232 } |
221 | 233 |
222 int compareTo(SourceSpan other) { | 234 int compareTo(SourceSpan other) { |
223 if (other is! FileSpan) return super.compareTo(other); | 235 // Check runtimeType to be resilient to external FileSpan implementations. |
Siggi Cherem (dart-lang)
2015/08/18 21:38:36
remove comment now that it's no longer needed (sam
nweiz
2015/08/18 22:01:25
Done.
| |
236 if (other is! _FileSpan) return super.compareTo(other); | |
224 | 237 |
225 FileSpan otherFile = other; | 238 _FileSpan otherFile = other; |
226 var result = _start.compareTo(otherFile._start); | 239 var result = _start.compareTo(otherFile._start); |
227 return result == 0 ? _end.compareTo(otherFile._end) : result; | 240 return result == 0 ? _end.compareTo(otherFile._end) : result; |
228 } | 241 } |
229 | 242 |
230 SourceSpan union(SourceSpan other) { | 243 SourceSpan union(SourceSpan other) { |
231 if (other is! FileSpan) return super.union(other); | 244 if (other is! FileSpan) return super.union(other); |
232 | 245 |
233 var span = expand(other); | 246 _FileSpan span = expand(other); |
234 var beginSpan = span._start == _start ? this : other; | 247 var beginSpan = span._start == _start ? this : other; |
235 var endSpan = span._end == _end ? this : other; | 248 var endSpan = span._end == _end ? this : other; |
236 | 249 |
237 if (beginSpan._end < endSpan._start) { | 250 if (beginSpan._end < endSpan._start) { |
238 throw new ArgumentError("Spans $this and $other are disjoint."); | 251 throw new ArgumentError("Spans $this and $other are disjoint."); |
239 } | 252 } |
240 | 253 |
241 return span; | 254 return span; |
242 } | 255 } |
243 | 256 |
244 bool operator ==(other) { | 257 bool operator ==(other) { |
258 // Check runtimeType to be resilient to external FileSpan implementations. | |
245 if (other is! FileSpan) return super == other; | 259 if (other is! FileSpan) return super == other; |
260 if (other is! _FileSpan) { | |
261 return super == other && sourceUrl == other.sourceUrl; | |
262 } | |
263 | |
246 return _start == other._start && _end == other._end && | 264 return _start == other._start && _end == other._end && |
247 sourceUrl == other.sourceUrl; | 265 sourceUrl == other.sourceUrl; |
248 } | 266 } |
249 | 267 |
250 int get hashCode => _start.hashCode + 5 * _end.hashCode + | |
251 7 * sourceUrl.hashCode; | |
252 | |
253 /// Returns a new span that covers both [this] and [other]. | 268 /// Returns a new span that covers both [this] and [other]. |
254 /// | 269 /// |
255 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text | 270 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text |
256 /// between the two will be covered by the returned span. | 271 /// between the two will be covered by the returned span. |
257 FileSpan expand(FileSpan other) { | 272 FileSpan expand(FileSpan other) { |
258 if (sourceUrl != other.sourceUrl) { | 273 if (sourceUrl != other.sourceUrl) { |
259 throw new ArgumentError("Source URLs \"${sourceUrl}\" and " | 274 throw new ArgumentError("Source URLs \"${sourceUrl}\" and " |
260 " \"${other.sourceUrl}\" don't match."); | 275 " \"${other.sourceUrl}\" don't match."); |
261 } | 276 } |
262 | 277 |
263 var start = math.min(this._start, other._start); | 278 // Check runtimeType to be resilient to external FileSpan implementations. |
264 var end = math.max(this._end, other._end); | 279 if (other is _FileSpan) { |
265 return new FileSpan._(file, start, end); | 280 var start = math.min(this._start, other._start); |
281 var end = math.max(this._end, other._end); | |
282 return new _FileSpan(file, start, end); | |
283 } else { | |
284 var start = math.min(this._start, other.start.offset); | |
285 var end = math.max(this._end, other.end.offset); | |
286 return new _FileSpan(file, start, end); | |
287 } | |
266 } | 288 } |
267 } | 289 } |
OLD | NEW |