LCOV - code coverage report
Current view: top level - source_span-1.8.1/lib/src - file.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 16 162 9.9 %
Date: 2021-11-28 14:37:50 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          15 :   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           0 :   @Deprecated('Will be removed in 2.0.0')
      56           0 :   SourceFile(String text, {url}) : this.decoded(text.runes, url: url);
      57             : 
      58             :   /// Creates a new source file from [text].
      59             :   ///
      60             :   /// [url] may be either a [String], a [Uri], or `null`.
      61           5 :   SourceFile.fromString(String text, {url})
      62          10 :       : this.decoded(text.codeUnits, url: url);
      63             : 
      64             :   /// Creates a new source file from a list of decoded code units.
      65             :   ///
      66             :   /// [url] may be either a [String], a [Uri], or `null`.
      67             :   ///
      68             :   /// Currently, if [decodedChars] contains characters larger than `0xFFFF`,
      69             :   /// they'll be treated as single characters rather than being split into
      70             :   /// surrogate pairs. **This behavior is deprecated**. For
      71             :   /// forwards-compatibility, callers should only pass in characters less than
      72             :   /// or equal to `0xFFFF`.
      73           5 :   SourceFile.decoded(Iterable<int> decodedChars, {url})
      74           5 :       : url = url is String ? Uri.parse(url) : url as Uri?,
      75          10 :         _decodedChars = Uint32List.fromList(decodedChars.toList()) {
      76          20 :     for (var i = 0; i < _decodedChars.length; i++) {
      77          10 :       var c = _decodedChars[i];
      78           5 :       if (c == _cr) {
      79             :         // Return not followed by newline is treated as a newline
      80           0 :         final j = i + 1;
      81           0 :         if (j >= _decodedChars.length || _decodedChars[j] != _lf) c = _lf;
      82             :       }
      83           5 :       if (c == _lf) _lineStarts.add(i + 1);
      84             :     }
      85             :   }
      86             : 
      87             :   /// Returns a span from [start] to [end] (exclusive).
      88             :   ///
      89             :   /// If [end] isn't passed, it defaults to the end of the file.
      90           5 :   FileSpan span(int start, [int? end]) {
      91           0 :     end ??= length;
      92           5 :     return _FileSpan(this, start, end);
      93             :   }
      94             : 
      95             :   /// Returns a location at [offset].
      96           0 :   FileLocation location(int offset) => FileLocation._(this, offset);
      97             : 
      98             :   /// Gets the 0-based line corresponding to [offset].
      99           0 :   int getLine(int offset) {
     100           0 :     if (offset < 0) {
     101           0 :       throw RangeError('Offset may not be negative, was $offset.');
     102           0 :     } else if (offset > length) {
     103           0 :       throw RangeError('Offset $offset must not be greater than the number '
     104           0 :           'of characters in the file, $length.');
     105             :     }
     106             : 
     107           0 :     if (offset < _lineStarts.first) return -1;
     108           0 :     if (offset >= _lineStarts.last) return _lineStarts.length - 1;
     109             : 
     110           0 :     if (_isNearCachedLine(offset)) return _cachedLine!;
     111             : 
     112           0 :     _cachedLine = _binarySearch(offset) - 1;
     113           0 :     return _cachedLine!;
     114             :   }
     115             : 
     116             :   /// Returns `true` if [offset] is near [_cachedLine].
     117             :   ///
     118             :   /// Checks on [_cachedLine] and the next line. If it's on the next line, it
     119             :   /// updates [_cachedLine] to point to that.
     120           0 :   bool _isNearCachedLine(int offset) {
     121           0 :     if (_cachedLine == null) return false;
     122           0 :     final cachedLine = _cachedLine!;
     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 = cachedLine + 1;
     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           0 :   int _binarySearch(int offset) {
     147             :     var min = 0;
     148           0 :     var max = _lineStarts.length - 1;
     149           0 :     while (min < max) {
     150           0 :       final 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           0 :   int getColumn(int offset, {int? line}) {
     166           0 :     if (offset < 0) {
     167           0 :       throw RangeError('Offset may not be negative, was $offset.');
     168           0 :     } else if (offset > length) {
     169           0 :       throw 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 RangeError('Line may not be negative, was $line.');
     177           0 :     } else if (line >= lines) {
     178           0 :       throw RangeError('Line $line must be less than the number of '
     179           0 :           'lines in the file, $lines.');
     180             :     }
     181             : 
     182           0 :     final lineStart = _lineStarts[line];
     183           0 :     if (lineStart > offset) {
     184           0 :       throw 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           0 :   int getOffset(int line, [int? column]) {
     194             :     column ??= 0;
     195             : 
     196           0 :     if (line < 0) {
     197           0 :       throw RangeError('Line may not be negative, was $line.');
     198           0 :     } else if (line >= lines) {
     199           0 :       throw 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 RangeError('Column may not be negative, was $column.');
     203             :     }
     204             : 
     205           0 :     final result = _lineStarts[line] + column;
     206           0 :     if (result > length ||
     207           0 :         (line + 1 < lines && result >= _lineStarts[line + 1])) {
     208           0 :       throw 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           0 :   String getText(int start, [int? end]) =>
     218           0 :       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             :   @override
     232             :   final int offset;
     233             : 
     234           0 :   @override
     235           0 :   Uri? get sourceUrl => file.url;
     236             : 
     237           0 :   @override
     238           0 :   int get line => file.getLine(offset);
     239             : 
     240           0 :   @override
     241           0 :   int get column => file.getColumn(offset);
     242             : 
     243           0 :   FileLocation._(this.file, this.offset) {
     244           0 :     if (offset < 0) {
     245           0 :       throw RangeError('Offset may not be negative, was $offset.');
     246           0 :     } else if (offset > file.length) {
     247           0 :       throw RangeError('Offset $offset must not be greater than the number '
     248           0 :           'of characters in the file, ${file.length}.');
     249             :     }
     250             :   }
     251             : 
     252           0 :   @override
     253           0 :   FileSpan pointSpan() => _FileSpan(file, offset, offset);
     254             : }
     255             : 
     256             : /// A [SourceSpan] within a [SourceFile].
     257             : ///
     258             : /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column
     259             : /// values based on its offset and the contents of [file]. [SourceSpan.message]
     260             : /// is also able to provide more context then [SourceSpan.message], and
     261             : /// [SourceSpan.union] will return a [FileSpan] if possible.
     262             : ///
     263             : /// A [FileSpan] can be created using [SourceFile.span].
     264             : abstract class FileSpan implements SourceSpanWithContext {
     265             :   /// The [file] that `this` belongs to.
     266             :   SourceFile get file;
     267             : 
     268             :   @override
     269             :   FileLocation get start;
     270             : 
     271             :   @override
     272             :   FileLocation get end;
     273             : 
     274             :   /// Returns a new span that covers both `this` and [other].
     275             :   ///
     276             :   /// Unlike [union], [other] may be disjoint from `this`. If it is, the text
     277             :   /// between the two will be covered by the returned span.
     278             :   FileSpan expand(FileSpan other);
     279             : }
     280             : 
     281             : /// The implementation of [FileSpan].
     282             : ///
     283             : /// This is split into a separate class so that `is _FileSpan` checks can be run
     284             : /// to make certain operations more efficient. If we used `is FileSpan`, that
     285             : /// would break if external classes implemented the interface.
     286             : class _FileSpan extends SourceSpanMixin implements FileSpan {
     287             :   @override
     288             :   final SourceFile file;
     289             : 
     290             :   /// The offset of the beginning of the span.
     291             :   ///
     292             :   /// [start] is lazily generated from this to avoid allocating unnecessary
     293             :   /// objects.
     294             :   final int _start;
     295             : 
     296             :   /// The offset of the end of the span.
     297             :   ///
     298             :   /// [end] is lazily generated from this to avoid allocating unnecessary
     299             :   /// objects.
     300             :   final int _end;
     301             : 
     302           0 :   @override
     303           0 :   Uri? get sourceUrl => file.url;
     304             : 
     305           0 :   @override
     306           0 :   int get length => _end - _start;
     307             : 
     308           0 :   @override
     309           0 :   FileLocation get start => FileLocation._(file, _start);
     310             : 
     311           0 :   @override
     312           0 :   FileLocation get end => FileLocation._(file, _end);
     313             : 
     314           0 :   @override
     315           0 :   String get text => file.getText(_start, _end);
     316             : 
     317           0 :   @override
     318             :   String get context {
     319           0 :     final endLine = file.getLine(_end);
     320           0 :     final endColumn = file.getColumn(_end);
     321             : 
     322             :     int? endOffset;
     323           0 :     if (endColumn == 0 && endLine != 0) {
     324             :       // If [end] is at the very beginning of the line, the span covers the
     325             :       // previous newline, so we only want to include the previous line in the
     326             :       // context...
     327             : 
     328           0 :       if (length == 0) {
     329             :         // ...unless this is a point span, in which case we want to include the
     330             :         // next line (or the empty string if this is the end of the file).
     331           0 :         return endLine == file.lines - 1
     332             :             ? ''
     333           0 :             : file.getText(
     334           0 :                 file.getOffset(endLine), file.getOffset(endLine + 1));
     335             :       }
     336             : 
     337           0 :       endOffset = _end;
     338           0 :     } else if (endLine == file.lines - 1) {
     339             :       // If the span covers the last line of the file, the context should go all
     340             :       // the way to the end of the file.
     341           0 :       endOffset = file.length;
     342             :     } else {
     343             :       // Otherwise, the context should cover the full line on which [end]
     344             :       // appears.
     345           0 :       endOffset = file.getOffset(endLine + 1);
     346             :     }
     347             : 
     348           0 :     return file.getText(file.getOffset(file.getLine(_start)), endOffset);
     349             :   }
     350             : 
     351           5 :   _FileSpan(this.file, this._start, this._end) {
     352          15 :     if (_end < _start) {
     353           0 :       throw ArgumentError('End $_end must come after start $_start.');
     354          20 :     } else if (_end > file.length) {
     355           0 :       throw RangeError('End $_end must not be greater than the number '
     356           0 :           'of characters in the file, ${file.length}.');
     357          10 :     } else if (_start < 0) {
     358           0 :       throw RangeError('Start may not be negative, was $_start.');
     359             :     }
     360             :   }
     361             : 
     362           0 :   @override
     363             :   int compareTo(SourceSpan other) {
     364           0 :     if (other is! _FileSpan) return super.compareTo(other);
     365             : 
     366           0 :     final result = _start.compareTo(other._start);
     367           0 :     return result == 0 ? _end.compareTo(other._end) : result;
     368             :   }
     369             : 
     370           0 :   @override
     371             :   SourceSpan union(SourceSpan other) {
     372           0 :     if (other is! FileSpan) return super.union(other);
     373             : 
     374           0 :     final span = expand(other);
     375             : 
     376           0 :     if (other is _FileSpan) {
     377           0 :       if (_start > other._end || other._start > _end) {
     378           0 :         throw ArgumentError('Spans $this and $other are disjoint.');
     379             :       }
     380             :     } else {
     381           0 :       if (_start > other.end.offset || other.start.offset > _end) {
     382           0 :         throw ArgumentError('Spans $this and $other are disjoint.');
     383             :       }
     384             :     }
     385             : 
     386             :     return span;
     387             :   }
     388             : 
     389           0 :   @override
     390             :   bool operator ==(other) {
     391           0 :     if (other is! FileSpan) return super == other;
     392           0 :     if (other is! _FileSpan) {
     393           0 :       return super == other && sourceUrl == other.sourceUrl;
     394             :     }
     395             : 
     396           0 :     return _start == other._start &&
     397           0 :         _end == other._end &&
     398           0 :         sourceUrl == other.sourceUrl;
     399             :   }
     400             : 
     401             :   // Eliminates dart2js warning about overriding `==`, but not `hashCode`
     402           0 :   @override
     403           0 :   int get hashCode => super.hashCode;
     404             : 
     405             :   /// Returns a new span that covers both `this` and [other].
     406             :   ///
     407             :   /// Unlike [union], [other] may be disjoint from `this`. If it is, the text
     408             :   /// between the two will be covered by the returned span.
     409           0 :   @override
     410             :   FileSpan expand(FileSpan other) {
     411           0 :     if (sourceUrl != other.sourceUrl) {
     412           0 :       throw ArgumentError('Source URLs \"$sourceUrl\" and '
     413           0 :           " \"${other.sourceUrl}\" don't match.");
     414             :     }
     415             : 
     416           0 :     if (other is _FileSpan) {
     417           0 :       final start = math.min(_start, other._start);
     418           0 :       final end = math.max(_end, other._end);
     419           0 :       return _FileSpan(file, start, end);
     420             :     } else {
     421           0 :       final start = math.min(_start, other.start.offset);
     422           0 :       final end = math.max(_end, other.end.offset);
     423           0 :       return _FileSpan(file, start, end);
     424             :     }
     425             :   }
     426             : 
     427             :   /// See `SourceSpanExtension.subspan`.
     428           0 :   FileSpan subspan(int start, [int? end]) {
     429           0 :     RangeError.checkValidRange(start, end, length);
     430           0 :     if (start == 0 && (end == null || end == length)) return this;
     431           0 :     return file.span(_start + start, end == null ? _end : _start + end);
     432             :   }
     433             : }
     434             : 
     435             : // TODO(#52): Move these to instance methods in the next breaking release.
     436             : /// Extension methods on the [FileSpan] API.
     437             : extension FileSpanExtension on FileSpan {
     438             :   /// See `SourceSpanExtension.subspan`.
     439           0 :   FileSpan subspan(int start, [int? end]) {
     440           0 :     RangeError.checkValidRange(start, end, length);
     441           0 :     if (start == 0 && (end == null || end == length)) return this;
     442             : 
     443           0 :     final startOffset = this.start.offset;
     444           0 :     return file.span(
     445           0 :         startOffset + start, end == null ? this.end.offset : startOffset + end);
     446             :   }
     447             : }

Generated by: LCOV version 1.14