LCOV - code coverage report
Current view: top level - source_span-1.4.0/lib/src - file.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 4 115 3.5 %
Date: 2017-10-10 20:17:03 Functions: 0 0 -

          Line data    Source code
       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
       3             : // BSD-style license that can be found in the LICENSE file.
       4             : 
       5             : import 'dart:math' as math;
       6             : import 'dart:typed_data';
       7             : 
       8             : import 'location.dart';
       9             : import 'location_mixin.dart';
      10             : import 'span.dart';
      11             : import 'span_mixin.dart';
      12             : import 'span_with_context.dart';
      13             : 
      14             : // Constants to determine end-of-lines.
      15             : const int _LF = 10;
      16             : const int _CR = 13;
      17             : 
      18             : /// A class representing a source file.
      19             : ///
      20             : /// This doesn't necessarily have to correspond to a file on disk, just a chunk
      21             : /// of text usually with a URL associated with it.
      22             : class SourceFile {
      23             :   /// The URL where the source file is located.
      24             :   ///
      25             :   /// This may be null, indicating that the URL is unknown or unavailable.
      26             :   final Uri url;
      27             : 
      28             :   /// An array of offsets for each line beginning in the file.
      29             :   ///
      30             :   /// Each offset refers to the first character *after* the newline. If the
      31             :   /// source file has a trailing newline, the final offset won't actually be in
      32             :   /// the file.
      33             :   final _lineStarts = <int>[0];
      34             : 
      35             :   /// The code points of the characters in the file.
      36             :   final Uint32List _decodedChars;
      37             : 
      38             :   /// The length of the file in characters.
      39           0 :   int get length => _decodedChars.length;
      40             : 
      41             :   /// The number of lines in the file.
      42           0 :   int get lines => _lineStarts.length;
      43             : 
      44             :   /// The line that the offset fell on the last time [getLine] was called.
      45             :   ///
      46             :   /// In many cases, sequential calls to getLine() are for nearby, usually
      47             :   /// increasing offsets. In that case, we can find the line for an offset
      48             :   /// quickly by first checking to see if the offset is on the same line as the
      49             :   /// previous result.
      50             :   int _cachedLine;
      51             : 
      52             :   /// This constructor is deprecated.
      53             :   ///
      54             :   /// Use [new SourceFile.fromString] instead.
      55             :   @Deprecated("Will be removed in 2.0.0")
      56             :   SourceFile(String text, {url})
      57           0 :       : this.decoded(text.runes, url: url);
      58             : 
      59             :   /// Creates a new source file from [text].
      60             :   ///
      61             :   /// [url] may be either a [String], a [Uri], or `null`.
      62             :   SourceFile.fromString(String text, {url})
      63           0 :       : this.decoded(text.codeUnits, url: url);
      64             : 
      65             :   /// Creates a new source file from a list of decoded code units.
      66             :   ///
      67             :   /// [url] may be either a [String], a [Uri], or `null`.
      68             :   ///
      69             :   /// Currently, if [decodedChars] contains characters larger than `0xFFFF`,
      70             :   /// they'll be treated as single characters rather than being split into
      71             :   /// surrogate pairs. **This behavior is deprecated**. For
      72             :   /// forwards-compatibility, callers should only pass in characters less than
      73             :   /// or equal to `0xFFFF`.
      74             :   SourceFile.decoded(Iterable<int> decodedChars, {url})
      75           0 :       : url = url is String ? Uri.parse(url) : url,
      76           0 :         _decodedChars = new Uint32List.fromList(decodedChars.toList()) {
      77           0 :     for (var i = 0; i < _decodedChars.length; i++) {
      78           0 :       var c = _decodedChars[i];
      79           0 :       if (c == _CR) {
      80             :         // Return not followed by newline is treated as a newline
      81           0 :         var j = i + 1;
      82           0 :         if (j >= _decodedChars.length || _decodedChars[j] != _LF) c = _LF;
      83             :       }
      84           0 :       if (c == _LF) _lineStarts.add(i + 1);
      85             :     }
      86             :   }
      87             : 
      88             :   /// Returns a span in [this] from [start] to [end] (exclusive).
      89             :   ///
      90             :   /// If [end] isn't passed, it defaults to the end of the file.
      91             :   FileSpan span(int start, [int end]) {
      92           0 :     if (end == null) end = length - 1;
      93           0 :     return new _FileSpan(this, start, end);
      94             :   }
      95             : 
      96             :   /// Returns a location in [this] at [offset].
      97           0 :   FileLocation location(int offset) => new FileLocation._(this, offset);
      98             : 
      99             :   /// Gets the 0-based line corresponding to [offset].
     100             :   int getLine(int offset) {
     101           0 :     if (offset < 0) {
     102           0 :       throw new RangeError("Offset may not be negative, was $offset.");
     103           0 :     } else if (offset > length) {
     104           0 :       throw new RangeError("Offset $offset must not be greater than the number "
     105           0 :           "of characters in the file, $length.");
     106             :     }
     107             : 
     108           0 :     if (offset < _lineStarts.first) return -1;
     109           0 :     if (offset >= _lineStarts.last) return _lineStarts.length - 1;
     110             : 
     111           0 :     if (_isNearCachedLine(offset)) return _cachedLine;
     112             : 
     113           0 :     _cachedLine = _binarySearch(offset) - 1;
     114           0 :     return _cachedLine;
     115             :   }
     116             : 
     117             :   /// Returns `true` if [offset] is near [_cachedLine].
     118             :   ///
     119             :   /// Checks on [_cachedLine] and the next line. If it's on the next line, it
     120             :   /// updates [_cachedLine] to point to that.
     121             :   bool _isNearCachedLine(int offset) {
     122           0 :     if (_cachedLine == null) return false;
     123             : 
     124             :     // See if it's before the cached line.
     125           0 :     if (offset < _lineStarts[_cachedLine]) return false;
     126             : 
     127             :     // See if it's on the cached line.
     128           0 :     if (_cachedLine >= _lineStarts.length - 1 ||
     129           0 :         offset < _lineStarts[_cachedLine + 1]) {
     130             :       return true;
     131             :     }
     132             : 
     133             :     // See if it's on the next line.
     134           0 :     if (_cachedLine >= _lineStarts.length - 2 ||
     135           0 :         offset < _lineStarts[_cachedLine + 2]) {
     136           0 :       _cachedLine++;
     137             :       return true;
     138             :     }
     139             : 
     140             :     return false;
     141             :   }
     142             : 
     143             :   /// Binary search through [_lineStarts] to find the line containing [offset].
     144             :   ///
     145             :   /// Returns the index of the line in [_lineStarts].
     146             :   int _binarySearch(int offset) {
     147             :     int min = 0;
     148           0 :     int max = _lineStarts.length - 1;
     149           0 :     while (min < max) {
     150           0 :       var half = min + ((max - min) ~/ 2);
     151           0 :       if (_lineStarts[half] > offset) {
     152             :         max = half;
     153             :       } else {
     154           0 :         min = half + 1;
     155             :       }
     156             :     }
     157             : 
     158             :     return max;
     159             :   }
     160             : 
     161             :   /// Gets the 0-based column corresponding to [offset].
     162             :   ///
     163             :   /// If [line] is passed, it's assumed to be the line containing [offset] and
     164             :   /// is used to more efficiently compute the column.
     165             :   int getColumn(int offset, {int line}) {
     166           0 :     if (offset < 0) {
     167           0 :       throw new RangeError("Offset may not be negative, was $offset.");
     168           0 :     } else if (offset > length) {
     169           0 :       throw new RangeError("Offset $offset must be not be greater than the "
     170           0 :           "number of characters in the file, $length.");
     171             :     }
     172             : 
     173             :     if (line == null) {
     174           0 :       line = getLine(offset);
     175           0 :     } else if (line < 0) {
     176           0 :       throw new RangeError("Line may not be negative, was $line.");
     177           0 :     } else if (line >= lines) {
     178           0 :       throw new RangeError("Line $line must be less than the number of "
     179           0 :           "lines in the file, $lines.");
     180             :     }
     181             : 
     182           0 :     var lineStart = _lineStarts[line];
     183           0 :     if (lineStart > offset) {
     184           0 :       throw new RangeError("Line $line comes after offset $offset.");
     185             :     }
     186             : 
     187           0 :     return offset - lineStart;
     188             :   }
     189             : 
     190             :   /// Gets the offset for a [line] and [column].
     191             :   ///
     192             :   /// [column] defaults to 0.
     193             :   int getOffset(int line, [int column]) {
     194             :     if (column == null) column = 0;
     195             : 
     196           0 :     if (line < 0) {
     197           0 :       throw new RangeError("Line may not be negative, was $line.");
     198           0 :     } else if (line >= lines) {
     199           0 :       throw new RangeError("Line $line must be less than the number of "
     200           0 :           "lines in the file, $lines.");
     201           0 :     } else if (column < 0) {
     202           0 :       throw new RangeError("Column may not be negative, was $column.");
     203             :     }
     204             : 
     205           0 :     var result = _lineStarts[line] + column;
     206           0 :     if (result > length ||
     207           0 :         (line + 1 < lines && result >= _lineStarts[line + 1])) {
     208           0 :       throw new RangeError("Line $line doesn't have $column columns.");
     209             :     }
     210             : 
     211             :     return result;
     212             :   }
     213             : 
     214             :   /// Returns the text of the file from [start] to [end] (exclusive).
     215             :   ///
     216             :   /// If [end] isn't passed, it defaults to the end of the file.
     217             :   String getText(int start, [int end]) =>
     218           0 :       new String.fromCharCodes(_decodedChars.sublist(start, end));
     219             : }
     220             : 
     221             : /// A [SourceLocation] within a [SourceFile].
     222             : ///
     223             : /// Unlike the base [SourceLocation], [FileLocation] lazily computes its line
     224             : /// and column values based on its offset and the contents of [file].
     225             : ///
     226             : /// A [FileLocation] can be created using [SourceFile.location].
     227             : class FileLocation extends SourceLocationMixin implements SourceLocation {
     228             :   /// The [file] that [this] belongs to.
     229             :   final SourceFile file;
     230             : 
     231             :   final int offset;
     232           0 :   Uri get sourceUrl => file.url;
     233           0 :   int get line => file.getLine(offset);
     234           0 :   int get column => file.getColumn(offset);
     235             : 
     236           0 :   FileLocation._(this.file, this.offset) {
     237           0 :     if (offset < 0) {
     238           0 :       throw new RangeError("Offset may not be negative, was $offset.");
     239           0 :     } else if (offset > file.length) {
     240           0 :       throw new RangeError("Offset $offset must not be greater than the number "
     241           0 :           "of characters in the file, ${file.length}.");
     242             :     }
     243             :   }
     244             : 
     245           0 :   FileSpan pointSpan() => new _FileSpan(file, offset, offset);
     246             : }
     247             : 
     248             : /// A [SourceSpan] within a [SourceFile].
     249             : ///
     250             : /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column
     251             : /// values based on its offset and the contents of [file]. [FileSpan.message] is
     252             : /// also able to provide more context then [SourceSpan.message], and
     253             : /// [FileSpan.union] will return a [FileSpan] if possible.
     254             : ///
     255             : /// A [FileSpan] can be created using [SourceFile.span].
     256             : abstract class FileSpan implements SourceSpanWithContext {
     257             :   /// The [file] that [this] belongs to.
     258             :   SourceFile get file;
     259             : 
     260             :   FileLocation get start;
     261             :   FileLocation get end;
     262             : 
     263             :   /// Returns a new span that covers both [this] and [other].
     264             :   ///
     265             :   /// Unlike [union], [other] may be disjoint from [this]. If it is, the text
     266             :   /// between the two will be covered by the returned span.
     267             :   FileSpan expand(FileSpan other);
     268             : }
     269             : 
     270             : /// The implementation of [FileSpan].
     271             : ///
     272             : /// This is split into a separate class so that `is _FileSpan` checks can be run
     273             : /// to make certain operations more efficient. If we used `is FileSpan`, that
     274             : /// would break if external classes implemented the interface.
     275             : class _FileSpan extends SourceSpanMixin implements FileSpan {
     276             :   final SourceFile file;
     277             : 
     278             :   /// The offset of the beginning of the span.
     279             :   ///
     280             :   /// [start] is lazily generated from this to avoid allocating unnecessary
     281             :   /// objects.
     282             :   final int _start;
     283             : 
     284             :   /// The offset of the end of the span.
     285             :   ///
     286             :   /// [end] is lazily generated from this to avoid allocating unnecessary
     287             :   /// objects.
     288             :   final int _end;
     289             : 
     290           0 :   Uri get sourceUrl => file.url;
     291           0 :   int get length => _end - _start;
     292           0 :   FileLocation get start => new FileLocation._(file, _start);
     293           0 :   FileLocation get end => new FileLocation._(file, _end);
     294           0 :   String get text => file.getText(_start, _end);
     295           0 :   String get context => file.getText(file.getOffset(start.line),
     296           0 :       end.line == file.lines - 1 ? null : file.getOffset(end.line + 1));
     297             : 
     298           0 :   _FileSpan(this.file, this._start, this._end) {
     299           0 :     if (_end < _start) {
     300           0 :       throw new ArgumentError('End $_end must come after start $_start.');
     301           0 :     } else if (_end > file.length) {
     302           0 :       throw new RangeError("End $_end must not be greater than the number "
     303           0 :           "of characters in the file, ${file.length}.");
     304           0 :     } else if (_start < 0) {
     305           0 :       throw new RangeError("Start may not be negative, was $_start.");
     306             :     }
     307             :   }
     308             : 
     309             :   int compareTo(SourceSpan other) {
     310           0 :     if (other is! _FileSpan) return super.compareTo(other);
     311             : 
     312             :     _FileSpan otherFile = other;
     313           0 :     var result = _start.compareTo(otherFile._start);
     314           0 :     return result == 0 ? _end.compareTo(otherFile._end) : result;
     315             :   }
     316             : 
     317             :   SourceSpan union(SourceSpan other) {
     318           0 :     if (other is! FileSpan) return super.union(other);
     319             : 
     320             :     
     321           0 :     _FileSpan span = expand(other);
     322             : 
     323           0 :     if (other is _FileSpan) {
     324           0 :       if (this._start > other._end || other._start > this._end) {
     325           0 :         throw new ArgumentError("Spans $this and $other are disjoint.");
     326             :       }
     327             :     } else {
     328           0 :       if (this._start > other.end.offset || other.start.offset > this._end) {
     329           0 :         throw new ArgumentError("Spans $this and $other are disjoint.");
     330             :       }
     331             :     }
     332             : 
     333             :     return span;
     334             :   }
     335             : 
     336             :   bool operator ==(other) {
     337           0 :     if (other is! FileSpan) return super == other;
     338           0 :     if (other is! _FileSpan) {
     339           0 :       return super == other && sourceUrl == other.sourceUrl;
     340             :     }
     341             : 
     342           0 :     return _start == other._start && _end == other._end &&
     343           0 :         sourceUrl == other.sourceUrl;
     344             :   }
     345             : 
     346             :   // Eliminates dart2js warning about overriding `==`, but not `hashCode`
     347           0 :   int get hashCode => super.hashCode;
     348             : 
     349             :   /// Returns a new span that covers both [this] and [other].
     350             :   ///
     351             :   /// Unlike [union], [other] may be disjoint from [this]. If it is, the text
     352             :   /// between the two will be covered by the returned span.
     353             :   FileSpan expand(FileSpan other) {
     354           0 :     if (sourceUrl != other.sourceUrl) {
     355           0 :       throw new ArgumentError("Source URLs \"${sourceUrl}\" and "
     356           0 :           " \"${other.sourceUrl}\" don't match.");
     357             :     }
     358             : 
     359           0 :     if (other is _FileSpan) {
     360           5 :       var start = math.min(this._start, other._start);
     361           5 :       var end = math.max(this._end, other._end);
     362           0 :       return new _FileSpan(file, start, end);
     363             :     } else {
     364           5 :       var start = math.min(this._start, other.start.offset);
     365           5 :       var end = math.max(this._end, other.end.offset);
     366           0 :       return new _FileSpan(file, start, end);
     367             :     }
     368             :   }
     369             : }

Generated by: LCOV version 1.13