LCOV - code coverage report
Current view: top level - path-1.8.0/lib/src - context.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 71 305 23.3 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       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             : import 'dart:math' as math;
       6             : 
       7             : import '../path.dart' as p;
       8             : import 'characters.dart' as chars;
       9             : import 'internal_style.dart';
      10             : import 'parsed_path.dart';
      11             : import 'path_exception.dart';
      12             : import 'style.dart';
      13             : 
      14          22 : Context createInternal() => Context._internal();
      15             : 
      16             : /// An instantiable class for manipulating paths. Unlike the top-level
      17             : /// functions, this lets you explicitly select what platform the paths will use.
      18             : class Context {
      19             :   /// Creates a new path context for the given style and current directory.
      20             :   ///
      21             :   /// If [style] is omitted, it uses the host operating system's path style. If
      22             :   /// only [current] is omitted, it defaults ".". If *both* [style] and
      23             :   /// [current] are omitted, [current] defaults to the real current working
      24             :   /// directory.
      25             :   ///
      26             :   /// On the browser, [style] defaults to [Style.url] and [current] defaults to
      27             :   /// the current URL.
      28           0 :   factory Context({Style? style, String? current}) {
      29             :     if (current == null) {
      30             :       if (style == null) {
      31           0 :         current = p.current;
      32             :       } else {
      33             :         current = '.';
      34             :       }
      35             :     }
      36             : 
      37             :     if (style == null) {
      38           0 :       style = Style.platform;
      39           0 :     } else if (style is! InternalStyle) {
      40           0 :       throw ArgumentError('Only styles defined by the path package are '
      41             :           'allowed.');
      42             :     }
      43             : 
      44           0 :     return Context._(style as InternalStyle, current);
      45             :   }
      46             : 
      47             :   /// Create a [Context] to be used internally within path.
      48          11 :   Context._internal()
      49          11 :       : style = Style.platform as InternalStyle,
      50             :         _current = null;
      51             : 
      52           0 :   Context._(this.style, this._current);
      53             : 
      54             :   /// The style of path that this context works with.
      55             :   final InternalStyle style;
      56             : 
      57             :   /// The current directory given when Context was created. If null, current
      58             :   /// directory is evaluated from 'p.current'.
      59             :   final String? _current;
      60             : 
      61             :   /// The current directory that relative paths are relative to.
      62          33 :   String get current => _current ?? p.current;
      63             : 
      64             :   /// Gets the path separator for the context's [style]. On Mac and Linux,
      65             :   /// this is `/`. On Windows, it's `\`.
      66           0 :   String get separator => style.separator;
      67             : 
      68             :   /// Returns a new path with the given path parts appended to [current].
      69             :   ///
      70             :   /// Equivalent to [join()] with [current] as the first argument. Example:
      71             :   ///
      72             :   ///     var context = Context(current: '/root');
      73             :   ///     context.absolute('path', 'to', 'foo'); // -> '/root/path/to/foo'
      74             :   ///
      75             :   /// If [current] isn't absolute, this won't return an absolute path. Does not
      76             :   /// [normalize] or [canonicalize] paths.
      77           0 :   String absolute(String part1,
      78             :       [String? part2,
      79             :       String? part3,
      80             :       String? part4,
      81             :       String? part5,
      82             :       String? part6,
      83             :       String? part7]) {
      84           0 :     _validateArgList(
      85           0 :         'absolute', [part1, part2, part3, part4, part5, part6, part7]);
      86             : 
      87             :     // If there's a single absolute path, just return it. This is a lot faster
      88             :     // for the common case of `p.absolute(path)`.
      89           0 :     if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) {
      90             :       return part1;
      91             :     }
      92             : 
      93           0 :     return join(current, part1, part2, part3, part4, part5, part6, part7);
      94             :   }
      95             : 
      96             :   /// Gets the part of [path] after the last separator on the context's
      97             :   /// platform.
      98             :   ///
      99             :   ///     context.basename('path/to/foo.dart'); // -> 'foo.dart'
     100             :   ///     context.basename('path/to');          // -> 'to'
     101             :   ///
     102             :   /// Trailing separators are ignored.
     103             :   ///
     104             :   ///     context.basename('path/to/'); // -> 'to'
     105           0 :   String basename(String path) => _parse(path).basename;
     106             : 
     107             :   /// Gets the part of [path] after the last separator on the context's
     108             :   /// platform, and without any trailing file extension.
     109             :   ///
     110             :   ///     context.basenameWithoutExtension('path/to/foo.dart'); // -> 'foo'
     111             :   ///
     112             :   /// Trailing separators are ignored.
     113             :   ///
     114             :   ///     context.basenameWithoutExtension('path/to/foo.dart/'); // -> 'foo'
     115           0 :   String basenameWithoutExtension(String path) =>
     116           0 :       _parse(path).basenameWithoutExtension;
     117             : 
     118             :   /// Gets the part of [path] before the last separator.
     119             :   ///
     120             :   ///     context.dirname('path/to/foo.dart'); // -> 'path/to'
     121             :   ///     context.dirname('path/to');          // -> 'path'
     122             :   ///
     123             :   /// Trailing separators are ignored.
     124             :   ///
     125             :   ///     context.dirname('path/to/'); // -> 'path'
     126           0 :   String dirname(String path) {
     127           0 :     final parsed = _parse(path);
     128           0 :     parsed.removeTrailingSeparators();
     129           0 :     if (parsed.parts.isEmpty) return parsed.root ?? '.';
     130           0 :     if (parsed.parts.length == 1) return parsed.root ?? '.';
     131           0 :     parsed.parts.removeLast();
     132           0 :     parsed.separators.removeLast();
     133           0 :     parsed.removeTrailingSeparators();
     134           0 :     return parsed.toString();
     135             :   }
     136             : 
     137             :   /// Gets the file extension of [path]: the portion of [basename] from the last
     138             :   /// `.` to the end (including the `.` itself).
     139             :   ///
     140             :   ///     context.extension('path/to/foo.dart'); // -> '.dart'
     141             :   ///     context.extension('path/to/foo'); // -> ''
     142             :   ///     context.extension('path.to/foo'); // -> ''
     143             :   ///     context.extension('path/to/foo.dart.js'); // -> '.js'
     144             :   ///
     145             :   /// If the file name starts with a `.`, then it is not considered an
     146             :   /// extension:
     147             :   ///
     148             :   ///     context.extension('~/.bashrc');    // -> ''
     149             :   ///     context.extension('~/.notes.txt'); // -> '.txt'
     150             :   ///
     151             :   /// Takes an optional parameter `level` which makes possible to return
     152             :   /// multiple extensions having `level` number of dots. If `level` exceeds the
     153             :   /// number of dots, the full extension is returned. The value of `level` must
     154             :   /// be greater than 0, else `RangeError` is thrown.
     155             :   ///
     156             :   ///     context.extension('foo.bar.dart.js', 2);   // -> '.dart.js
     157             :   ///     context.extension('foo.bar.dart.js', 3);   // -> '.bar.dart.js'
     158             :   ///     context.extension('foo.bar.dart.js', 10);  // -> '.bar.dart.js'
     159             :   ///     context.extension('path/to/foo.bar.dart.js', 2);  // -> '.dart.js'
     160           0 :   String extension(String path, [int level = 1]) =>
     161           0 :       _parse(path).extension(level);
     162             : 
     163             :   /// Returns the root of [path] if it's absolute, or an empty string if it's
     164             :   /// relative.
     165             :   ///
     166             :   ///     // Unix
     167             :   ///     context.rootPrefix('path/to/foo'); // -> ''
     168             :   ///     context.rootPrefix('/path/to/foo'); // -> '/'
     169             :   ///
     170             :   ///     // Windows
     171             :   ///     context.rootPrefix(r'path\to\foo'); // -> ''
     172             :   ///     context.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
     173             :   ///     context.rootPrefix(r'\\server\share\a\b'); // -> r'\\server\share'
     174             :   ///
     175             :   ///     // URL
     176             :   ///     context.rootPrefix('path/to/foo'); // -> ''
     177             :   ///     context.rootPrefix('https://dart.dev/path/to/foo');
     178             :   ///       // -> 'https://dart.dev'
     179           0 :   String rootPrefix(String path) => path.substring(0, style.rootLength(path));
     180             : 
     181             :   /// Returns `true` if [path] is an absolute path and `false` if it is a
     182             :   /// relative path.
     183             :   ///
     184             :   /// On POSIX systems, absolute paths start with a `/` (forward slash). On
     185             :   /// Windows, an absolute path starts with `\\`, or a drive letter followed by
     186             :   /// `:/` or `:\`. For URLs, absolute paths either start with a protocol and
     187             :   /// optional hostname (e.g. `https://dart.dev`, `file://`) or with a `/`.
     188             :   ///
     189             :   /// URLs that start with `/` are known as "root-relative", since they're
     190             :   /// relative to the root of the current URL. Since root-relative paths are
     191             :   /// still absolute in every other sense, [isAbsolute] will return true for
     192             :   /// them. They can be detected using [isRootRelative].
     193          44 :   bool isAbsolute(String path) => style.rootLength(path) > 0;
     194             : 
     195             :   /// Returns `true` if [path] is a relative path and `false` if it is absolute.
     196             :   /// On POSIX systems, absolute paths start with a `/` (forward slash). On
     197             :   /// Windows, an absolute path starts with `\\`, or a drive letter followed by
     198             :   /// `:/` or `:\`.
     199          22 :   bool isRelative(String path) => !isAbsolute(path);
     200             : 
     201             :   /// Returns `true` if [path] is a root-relative path and `false` if it's not.
     202             :   ///
     203             :   /// URLs that start with `/` are known as "root-relative", since they're
     204             :   /// relative to the root of the current URL. Since root-relative paths are
     205             :   /// still absolute in every other sense, [isAbsolute] will return true for
     206             :   /// them. They can be detected using [isRootRelative].
     207             :   ///
     208             :   /// No POSIX and Windows paths are root-relative.
     209          33 :   bool isRootRelative(String path) => style.isRootRelative(path);
     210             : 
     211             :   /// Joins the given path parts into a single path. Example:
     212             :   ///
     213             :   ///     context.join('path', 'to', 'foo'); // -> 'path/to/foo'
     214             :   ///
     215             :   /// If any part ends in a path separator, then a redundant separator will not
     216             :   /// be added:
     217             :   ///
     218             :   ///     context.join('path/', 'to', 'foo'); // -> 'path/to/foo
     219             :   ///
     220             :   /// If a part is an absolute path, then anything before that will be ignored:
     221             :   ///
     222             :   ///     context.join('path', '/to', 'foo'); // -> '/to/foo'
     223             :   ///
     224           0 :   String join(String part1,
     225             :       [String? part2,
     226             :       String? part3,
     227             :       String? part4,
     228             :       String? part5,
     229             :       String? part6,
     230             :       String? part7,
     231             :       String? part8]) {
     232           0 :     final parts = <String?>[
     233             :       part1,
     234             :       part2,
     235             :       part3,
     236             :       part4,
     237             :       part5,
     238             :       part6,
     239             :       part7,
     240             :       part8
     241             :     ];
     242           0 :     _validateArgList('join', parts);
     243           0 :     return joinAll(parts.whereType<String>());
     244             :   }
     245             : 
     246             :   /// Joins the given path parts into a single path. Example:
     247             :   ///
     248             :   ///     context.joinAll(['path', 'to', 'foo']); // -> 'path/to/foo'
     249             :   ///
     250             :   /// If any part ends in a path separator, then a redundant separator will not
     251             :   /// be added:
     252             :   ///
     253             :   ///     context.joinAll(['path/', 'to', 'foo']); // -> 'path/to/foo
     254             :   ///
     255             :   /// If a part is an absolute path, then anything before that will be ignored:
     256             :   ///
     257             :   ///     context.joinAll(['path', '/to', 'foo']); // -> '/to/foo'
     258             :   ///
     259             :   /// For a fixed number of parts, [join] is usually terser.
     260           0 :   String joinAll(Iterable<String> parts) {
     261           0 :     final buffer = StringBuffer();
     262             :     var needsSeparator = false;
     263             :     var isAbsoluteAndNotRootRelative = false;
     264             : 
     265           0 :     for (var part in parts.where((part) => part != '')) {
     266           0 :       if (isRootRelative(part) && isAbsoluteAndNotRootRelative) {
     267             :         // If the new part is root-relative, it preserves the previous root but
     268             :         // replaces the path after it.
     269           0 :         final parsed = _parse(part);
     270           0 :         final path = buffer.toString();
     271           0 :         parsed.root =
     272           0 :             path.substring(0, style.rootLength(path, withDrive: true));
     273           0 :         if (style.needsSeparator(parsed.root!)) {
     274           0 :           parsed.separators[0] = style.separator;
     275             :         }
     276           0 :         buffer.clear();
     277           0 :         buffer.write(parsed.toString());
     278           0 :       } else if (isAbsolute(part)) {
     279           0 :         isAbsoluteAndNotRootRelative = !isRootRelative(part);
     280             :         // An absolute path discards everything before it.
     281           0 :         buffer.clear();
     282           0 :         buffer.write(part);
     283             :       } else {
     284           0 :         if (part.isNotEmpty && style.containsSeparator(part[0])) {
     285             :           // The part starts with a separator, so we don't need to add one.
     286             :         } else if (needsSeparator) {
     287           0 :           buffer.write(separator);
     288             :         }
     289             : 
     290           0 :         buffer.write(part);
     291             :       }
     292             : 
     293             :       // Unless this part ends with a separator, we'll need to add one before
     294             :       // the next part.
     295           0 :       needsSeparator = style.needsSeparator(part);
     296             :     }
     297             : 
     298           0 :     return buffer.toString();
     299             :   }
     300             : 
     301             :   /// Splits [path] into its components using the current platform's
     302             :   /// [separator]. Example:
     303             :   ///
     304             :   ///     context.split('path/to/foo'); // -> ['path', 'to', 'foo']
     305             :   ///
     306             :   /// The path will *not* be normalized before splitting.
     307             :   ///
     308             :   ///     context.split('path/../foo'); // -> ['path', '..', 'foo']
     309             :   ///
     310             :   /// If [path] is absolute, the root directory will be the first element in the
     311             :   /// array. Example:
     312             :   ///
     313             :   ///     // Unix
     314             :   ///     context.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
     315             :   ///
     316             :   ///     // Windows
     317             :   ///     context.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
     318             :   ///     context.split(r'\\server\share\path\to\foo');
     319             :   ///       // -> [r'\\server\share', 'foo', 'bar', 'baz']
     320             :   ///
     321             :   ///     // Browser
     322             :   ///     context.split('https://dart.dev/path/to/foo');
     323             :   ///       // -> ['https://dart.dev', 'path', 'to', 'foo']
     324          11 :   List<String> split(String path) {
     325          11 :     final parsed = _parse(path);
     326             :     // Filter out empty parts that exist due to multiple separators in a row.
     327          66 :     parsed.parts = parsed.parts.where((part) => part.isNotEmpty).toList();
     328          44 :     if (parsed.root != null) parsed.parts.insert(0, parsed.root!);
     329          11 :     return parsed.parts;
     330             :   }
     331             : 
     332             :   /// Canonicalizes [path].
     333             :   ///
     334             :   /// This is guaranteed to return the same path for two different input paths
     335             :   /// if and only if both input paths point to the same location. Unlike
     336             :   /// [normalize], it returns absolute paths when possible and canonicalizes
     337             :   /// ASCII case on Windows.
     338             :   ///
     339             :   /// Note that this does not resolve symlinks.
     340             :   ///
     341             :   /// If you want a map that uses path keys, it's probably more efficient to use
     342             :   /// a Map with [equals] and [hash] specified as the callbacks to use for keys
     343             :   /// than it is to canonicalize every key.
     344           0 :   String canonicalize(String path) {
     345           0 :     path = absolute(path);
     346           0 :     if (style != Style.windows && !_needsNormalization(path)) return path;
     347             : 
     348           0 :     final parsed = _parse(path);
     349           0 :     parsed.normalize(canonicalize: true);
     350           0 :     return parsed.toString();
     351             :   }
     352             : 
     353             :   /// Normalizes [path], simplifying it by handling `..`, and `.`, and
     354             :   /// removing redundant path separators whenever possible.
     355             :   ///
     356             :   /// Note that this is *not* guaranteed to return the same result for two
     357             :   /// equivalent input paths. For that, see [canonicalize]. Or, if you're using
     358             :   /// paths as map keys use [equals] and [hash] as the key callbacks.
     359             :   ///
     360             :   ///     context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
     361          11 :   String normalize(String path) {
     362          11 :     if (!_needsNormalization(path)) return path;
     363             : 
     364           0 :     final parsed = _parse(path);
     365           0 :     parsed.normalize();
     366           0 :     return parsed.toString();
     367             :   }
     368             : 
     369             :   /// Returns whether [path] needs to be normalized.
     370          11 :   bool _needsNormalization(String path) {
     371             :     var start = 0;
     372          11 :     final codeUnits = path.codeUnits;
     373             :     int? previousPrevious;
     374             :     int? previous;
     375             : 
     376             :     // Skip past the root before we start looking for snippets that need
     377             :     // normalization. We want to normalize "//", but not when it's part of
     378             :     // "http://".
     379          22 :     final root = style.rootLength(path);
     380          11 :     if (root != 0) {
     381             :       start = root;
     382             :       previous = chars.slash;
     383             : 
     384             :       // On Windows, the root still needs to be normalized if it contains a
     385             :       // forward slash.
     386          33 :       if (style == Style.windows) {
     387           0 :         for (var i = 0; i < root; i++) {
     388           0 :           if (codeUnits[i] == chars.slash) return true;
     389             :         }
     390             :       }
     391             :     }
     392             : 
     393          33 :     for (var i = start; i < codeUnits.length; i++) {
     394          11 :       final codeUnit = codeUnits[i];
     395          22 :       if (style.isSeparator(codeUnit)) {
     396             :         // Forward slashes in Windows paths are normalized to backslashes.
     397          33 :         if (style == Style.windows && codeUnit == chars.slash) return true;
     398             : 
     399             :         // Multiple separators are normalized to single separators.
     400          22 :         if (previous != null && style.isSeparator(previous)) return true;
     401             : 
     402             :         // Single dots and double dots are normalized to directory traversals.
     403             :         //
     404             :         // This can return false positives for ".../", but that's unlikely
     405             :         // enough that it's probably not going to cause performance issues.
     406          11 :         if (previous == chars.period &&
     407             :             (previousPrevious == null ||
     408           0 :                 previousPrevious == chars.period ||
     409           0 :                 style.isSeparator(previousPrevious))) {
     410             :           return true;
     411             :         }
     412             :       }
     413             : 
     414             :       previousPrevious = previous;
     415             :       previous = codeUnit;
     416             :     }
     417             : 
     418             :     // Empty paths are normalized to ".".
     419             :     if (previous == null) return true;
     420             : 
     421             :     // Trailing separators are removed.
     422          22 :     if (style.isSeparator(previous)) return true;
     423             : 
     424             :     // Single dots and double dots are normalized to directory traversals.
     425          11 :     if (previous == chars.period &&
     426             :         (previousPrevious == null ||
     427           0 :             style.isSeparator(previousPrevious) ||
     428           0 :             previousPrevious == chars.period)) {
     429             :       return true;
     430             :     }
     431             : 
     432             :     return false;
     433             :   }
     434             : 
     435             :   /// Attempts to convert [path] to an equivalent relative path relative to
     436             :   /// [current].
     437             :   ///
     438             :   ///     var context = Context(current: '/root/path');
     439             :   ///     context.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
     440             :   ///     context.relative('/root/other.dart'); // -> '../other.dart'
     441             :   ///
     442             :   /// If the [from] argument is passed, [path] is made relative to that instead.
     443             :   ///
     444             :   ///     context.relative('/root/path/a/b.dart',
     445             :   ///         from: '/root/path'); // -> 'a/b.dart'
     446             :   ///     context.relative('/root/other.dart',
     447             :   ///         from: '/root/path'); // -> '../other.dart'
     448             :   ///
     449             :   /// If [path] and/or [from] are relative paths, they are assumed to be
     450             :   /// relative to [current].
     451             :   ///
     452             :   /// Since there is no relative path from one drive letter to another on
     453             :   /// Windows, this will return an absolute path in that case.
     454             :   ///
     455             :   ///     context.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'
     456             :   ///
     457             :   /// This will also return an absolute path if an absolute [path] is passed to
     458             :   /// a context with a relative path for [current].
     459             :   ///
     460             :   ///     var context = Context(r'some/relative/path');
     461             :   ///     context.relative(r'/absolute/path'); // -> '/absolute/path'
     462             :   ///
     463             :   /// If [current] is relative, it may be impossible to determine a path from
     464             :   /// [from] to [path]. For example, if [current] and [path] are "." and [from]
     465             :   /// is "/", no path can be determined. In this case, a [PathException] will be
     466             :   /// thrown.
     467          11 :   String relative(String path, {String? from}) {
     468             :     // Avoid expensive computation if the path is already relative.
     469          11 :     if (from == null && isRelative(path)) return normalize(path);
     470             : 
     471          11 :     from = from == null ? current : absolute(from);
     472             : 
     473             :     // We can't determine the path from a relative path to an absolute path.
     474          11 :     if (isRelative(from) && isAbsolute(path)) {
     475           0 :       return normalize(path);
     476             :     }
     477             : 
     478             :     // If the given path is relative, resolve it relative to the context's
     479             :     // current directory.
     480          22 :     if (isRelative(path) || isRootRelative(path)) {
     481           0 :       path = absolute(path);
     482             :     }
     483             : 
     484             :     // If the path is still relative and `from` is absolute, we're unable to
     485             :     // find a path from `from` to `path`.
     486          11 :     if (isRelative(path) && isAbsolute(from)) {
     487           0 :       throw PathException('Unable to find a path to "$path" from "$from".');
     488             :     }
     489             : 
     490          22 :     final fromParsed = _parse(from)..normalize();
     491          22 :     final pathParsed = _parse(path)..normalize();
     492             : 
     493          55 :     if (fromParsed.parts.isNotEmpty && fromParsed.parts[0] == '.') {
     494           0 :       return pathParsed.toString();
     495             :     }
     496             : 
     497             :     // If the root prefixes don't match (for example, different drive letters
     498             :     // on Windows), then there is no relative path, so just return the absolute
     499             :     // one. In Windows, drive letters are case-insenstive and we allow
     500             :     // calculation of relative paths, even if a path has not been normalized.
     501          33 :     if (fromParsed.root != pathParsed.root &&
     502           0 :         ((fromParsed.root == null || pathParsed.root == null) ||
     503           0 :             !style.pathsEqual(fromParsed.root!, pathParsed.root!))) {
     504           0 :       return pathParsed.toString();
     505             :     }
     506             : 
     507             :     // Strip off their common prefix.
     508          22 :     while (fromParsed.parts.isNotEmpty &&
     509          22 :         pathParsed.parts.isNotEmpty &&
     510          66 :         style.pathsEqual(fromParsed.parts[0], pathParsed.parts[0])) {
     511          22 :       fromParsed.parts.removeAt(0);
     512          22 :       fromParsed.separators.removeAt(1);
     513          22 :       pathParsed.parts.removeAt(0);
     514          22 :       pathParsed.separators.removeAt(1);
     515             :     }
     516             : 
     517             :     // If there are any directories left in the from path, we need to walk up
     518             :     // out of them. If a directory left in the from path is '..', it cannot
     519             :     // be cancelled by adding a '..'.
     520          22 :     if (fromParsed.parts.isNotEmpty && fromParsed.parts[0] == '..') {
     521           0 :       throw PathException('Unable to find a path to "$path" from "$from".');
     522             :     }
     523          55 :     pathParsed.parts.insertAll(0, List.filled(fromParsed.parts.length, '..'));
     524          22 :     pathParsed.separators[0] = '';
     525          11 :     pathParsed.separators
     526          66 :         .insertAll(1, List.filled(fromParsed.parts.length, style.separator));
     527             : 
     528             :     // Corner case: the paths completely collapsed.
     529          22 :     if (pathParsed.parts.isEmpty) return '.';
     530             : 
     531             :     // Corner case: path was '.' and some '..' directories were added in front.
     532             :     // Don't add a final '/.' in that case.
     533          66 :     if (pathParsed.parts.length > 1 && pathParsed.parts.last == '.') {
     534           0 :       pathParsed.parts.removeLast();
     535           0 :       pathParsed.separators
     536           0 :         ..removeLast()
     537           0 :         ..removeLast()
     538           0 :         ..add('');
     539             :     }
     540             : 
     541             :     // Make it relative.
     542          11 :     pathParsed.root = '';
     543          11 :     pathParsed.removeTrailingSeparators();
     544             : 
     545          11 :     return pathParsed.toString();
     546             :   }
     547             : 
     548             :   /// Returns `true` if [child] is a path beneath `parent`, and `false`
     549             :   /// otherwise.
     550             :   ///
     551             :   ///     path.isWithin('/root/path', '/root/path/a'); // -> true
     552             :   ///     path.isWithin('/root/path', '/root/other'); // -> false
     553             :   ///     path.isWithin('/root/path', '/root/path'); // -> false
     554           0 :   bool isWithin(String parent, String child) =>
     555           0 :       _isWithinOrEquals(parent, child) == _PathRelation.within;
     556             : 
     557             :   /// Returns `true` if [path1] points to the same location as [path2], and
     558             :   /// `false` otherwise.
     559             :   ///
     560             :   /// The [hash] function returns a hash code that matches these equality
     561             :   /// semantics.
     562           0 :   bool equals(String path1, String path2) =>
     563           0 :       _isWithinOrEquals(path1, path2) == _PathRelation.equal;
     564             : 
     565             :   /// Compares two paths and returns an enum value indicating their relationship
     566             :   /// to one another.
     567             :   ///
     568             :   /// This never returns [_PathRelation.inconclusive].
     569           0 :   _PathRelation _isWithinOrEquals(String parent, String child) {
     570             :     // Make both paths the same level of relative. We're only able to do the
     571             :     // quick comparison if both paths are in the same format, and making a path
     572             :     // absolute is faster than making it relative.
     573           0 :     final parentIsAbsolute = isAbsolute(parent);
     574           0 :     final childIsAbsolute = isAbsolute(child);
     575             :     if (parentIsAbsolute && !childIsAbsolute) {
     576           0 :       child = absolute(child);
     577           0 :       if (style.isRootRelative(parent)) parent = absolute(parent);
     578             :     } else if (childIsAbsolute && !parentIsAbsolute) {
     579           0 :       parent = absolute(parent);
     580           0 :       if (style.isRootRelative(child)) child = absolute(child);
     581             :     } else if (childIsAbsolute && parentIsAbsolute) {
     582           0 :       final childIsRootRelative = style.isRootRelative(child);
     583           0 :       final parentIsRootRelative = style.isRootRelative(parent);
     584             : 
     585             :       if (childIsRootRelative && !parentIsRootRelative) {
     586           0 :         child = absolute(child);
     587             :       } else if (parentIsRootRelative && !childIsRootRelative) {
     588           0 :         parent = absolute(parent);
     589             :       }
     590             :     }
     591             : 
     592           0 :     final result = _isWithinOrEqualsFast(parent, child);
     593           0 :     if (result != _PathRelation.inconclusive) return result;
     594             : 
     595             :     String relative;
     596             :     try {
     597           0 :       relative = this.relative(child, from: parent);
     598           0 :     } on PathException catch (_) {
     599             :       // If no relative path from [parent] to [child] is found, [child]
     600             :       // definitely isn't a child of [parent].
     601             :       return _PathRelation.different;
     602             :     }
     603             : 
     604           0 :     if (!isRelative(relative)) return _PathRelation.different;
     605           0 :     if (relative == '.') return _PathRelation.equal;
     606           0 :     if (relative == '..') return _PathRelation.different;
     607           0 :     return (relative.length >= 3 &&
     608           0 :             relative.startsWith('..') &&
     609           0 :             style.isSeparator(relative.codeUnitAt(2)))
     610             :         ? _PathRelation.different
     611             :         : _PathRelation.within;
     612             :   }
     613             : 
     614             :   /// An optimized implementation of [_isWithinOrEquals] that doesn't handle a
     615             :   /// few complex cases.
     616           0 :   _PathRelation _isWithinOrEqualsFast(String parent, String child) {
     617             :     // Normally we just bail when we see "." path components, but we can handle
     618             :     // a single dot easily enough.
     619           0 :     if (parent == '.') parent = '';
     620             : 
     621           0 :     final parentRootLength = style.rootLength(parent);
     622           0 :     final childRootLength = style.rootLength(child);
     623             : 
     624             :     // If the roots aren't the same length, we know both paths are absolute or
     625             :     // both are root-relative, and thus that the roots are meaningfully
     626             :     // different.
     627             :     //
     628             :     //     isWithin("C:/bar", "//foo/bar/baz") //=> false
     629             :     //     isWithin("http://example.com/", "http://google.com/bar") //=> false
     630           0 :     if (parentRootLength != childRootLength) return _PathRelation.different;
     631             : 
     632             :     // Make sure that the roots are textually the same as well.
     633             :     //
     634             :     //     isWithin("C:/bar", "D:/bar/baz") //=> false
     635             :     //     isWithin("http://example.com/", "http://example.org/bar") //=> false
     636           0 :     for (var i = 0; i < parentRootLength; i++) {
     637           0 :       final parentCodeUnit = parent.codeUnitAt(i);
     638           0 :       final childCodeUnit = child.codeUnitAt(i);
     639           0 :       if (!style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
     640             :         return _PathRelation.different;
     641             :       }
     642             :     }
     643             : 
     644             :     // Start by considering the last code unit as a separator, since
     645             :     // semantically we're starting at a new path component even if we're
     646             :     // comparing relative paths.
     647             :     var lastCodeUnit = chars.slash;
     648             : 
     649             :     /// The index of the last separator in [parent].
     650             :     int? lastParentSeparator;
     651             : 
     652             :     // Iterate through both paths as long as they're semantically identical.
     653             :     var parentIndex = parentRootLength;
     654             :     var childIndex = childRootLength;
     655           0 :     while (parentIndex < parent.length && childIndex < child.length) {
     656           0 :       var parentCodeUnit = parent.codeUnitAt(parentIndex);
     657           0 :       var childCodeUnit = child.codeUnitAt(childIndex);
     658           0 :       if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
     659           0 :         if (style.isSeparator(parentCodeUnit)) {
     660             :           lastParentSeparator = parentIndex;
     661             :         }
     662             : 
     663             :         lastCodeUnit = parentCodeUnit;
     664           0 :         parentIndex++;
     665           0 :         childIndex++;
     666             :         continue;
     667             :       }
     668             : 
     669             :       // Ignore multiple separators in a row.
     670           0 :       if (style.isSeparator(parentCodeUnit) &&
     671           0 :           style.isSeparator(lastCodeUnit)) {
     672             :         lastParentSeparator = parentIndex;
     673           0 :         parentIndex++;
     674             :         continue;
     675           0 :       } else if (style.isSeparator(childCodeUnit) &&
     676           0 :           style.isSeparator(lastCodeUnit)) {
     677           0 :         childIndex++;
     678             :         continue;
     679             :       }
     680             : 
     681             :       // If a dot comes after a separator, it may be a directory traversal
     682             :       // operator. To check that, we need to know if it's followed by either
     683             :       // "/" or "./". Otherwise, it's just a normal non-matching character.
     684             :       //
     685             :       //     isWithin("foo/./bar", "foo/bar/baz") //=> true
     686             :       //     isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
     687           0 :       if (parentCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) {
     688           0 :         parentIndex++;
     689             : 
     690             :         // We've hit "/." at the end of the parent path, which we can ignore,
     691             :         // since the paths were equivalent up to this point.
     692           0 :         if (parentIndex == parent.length) break;
     693           0 :         parentCodeUnit = parent.codeUnitAt(parentIndex);
     694             : 
     695             :         // We've hit "/./", which we can ignore.
     696           0 :         if (style.isSeparator(parentCodeUnit)) {
     697             :           lastParentSeparator = parentIndex;
     698           0 :           parentIndex++;
     699             :           continue;
     700             :         }
     701             : 
     702             :         // We've hit "/..", which may be a directory traversal operator that
     703             :         // we can't handle on the fast track.
     704           0 :         if (parentCodeUnit == chars.period) {
     705           0 :           parentIndex++;
     706           0 :           if (parentIndex == parent.length ||
     707           0 :               style.isSeparator(parent.codeUnitAt(parentIndex))) {
     708             :             return _PathRelation.inconclusive;
     709             :           }
     710             :         }
     711             : 
     712             :         // If this isn't a directory traversal, fall through so we hit the
     713             :         // normal handling for mismatched paths.
     714             :       }
     715             : 
     716             :       // This is the same logic as above, but for the child path instead of the
     717             :       // parent.
     718           0 :       if (childCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) {
     719           0 :         childIndex++;
     720           0 :         if (childIndex == child.length) break;
     721           0 :         childCodeUnit = child.codeUnitAt(childIndex);
     722             : 
     723           0 :         if (style.isSeparator(childCodeUnit)) {
     724           0 :           childIndex++;
     725             :           continue;
     726             :         }
     727             : 
     728           0 :         if (childCodeUnit == chars.period) {
     729           0 :           childIndex++;
     730           0 :           if (childIndex == child.length ||
     731           0 :               style.isSeparator(child.codeUnitAt(childIndex))) {
     732             :             return _PathRelation.inconclusive;
     733             :           }
     734             :         }
     735             :       }
     736             : 
     737             :       // If we're here, we've hit two non-matching, non-significant characters.
     738             :       // As long as the remainders of the two paths don't have any unresolved
     739             :       // ".." components, we can be confident that [child] is not within
     740             :       // [parent].
     741           0 :       final childDirection = _pathDirection(child, childIndex);
     742           0 :       if (childDirection != _PathDirection.belowRoot) {
     743             :         return _PathRelation.inconclusive;
     744             :       }
     745             : 
     746           0 :       final parentDirection = _pathDirection(parent, parentIndex);
     747           0 :       if (parentDirection != _PathDirection.belowRoot) {
     748             :         return _PathRelation.inconclusive;
     749             :       }
     750             : 
     751             :       return _PathRelation.different;
     752             :     }
     753             : 
     754             :     // If the child is shorter than the parent, it's probably not within the
     755             :     // parent. The only exception is if the parent has some weird ".." stuff
     756             :     // going on, in which case we do the slow check.
     757             :     //
     758             :     //     isWithin("foo/bar/baz", "foo/bar") //=> false
     759             :     //     isWithin("foo/bar/baz/../..", "foo/bar") //=> true
     760           0 :     if (childIndex == child.length) {
     761           0 :       if (parentIndex == parent.length ||
     762           0 :           style.isSeparator(parent.codeUnitAt(parentIndex))) {
     763             :         lastParentSeparator = parentIndex;
     764             :       } else {
     765           0 :         lastParentSeparator ??= math.max(0, parentRootLength - 1);
     766             :       }
     767             : 
     768           0 :       final direction = _pathDirection(parent, lastParentSeparator);
     769           0 :       if (direction == _PathDirection.atRoot) return _PathRelation.equal;
     770           0 :       return direction == _PathDirection.aboveRoot
     771             :           ? _PathRelation.inconclusive
     772             :           : _PathRelation.different;
     773             :     }
     774             : 
     775             :     // We've reached the end of the parent path, which means it's time to make a
     776             :     // decision. Before we do, though, we'll check the rest of the child to see
     777             :     // what that tells us.
     778           0 :     final direction = _pathDirection(child, childIndex);
     779             : 
     780             :     // If there are no more components in the child, then it's the same as
     781             :     // the parent.
     782             :     //
     783             :     //     isWithin("foo/bar", "foo/bar") //=> false
     784             :     //     isWithin("foo/bar", "foo/bar//") //=> false
     785             :     //     equals("foo/bar", "foo/bar") //=> true
     786             :     //     equals("foo/bar", "foo/bar//") //=> true
     787           0 :     if (direction == _PathDirection.atRoot) return _PathRelation.equal;
     788             : 
     789             :     // If there are unresolved ".." components in the child, no decision we make
     790             :     // will be valid. We'll abort and do the slow check instead.
     791             :     //
     792             :     //     isWithin("foo/bar", "foo/bar/..") //=> false
     793             :     //     isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
     794             :     //     isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
     795           0 :     if (direction == _PathDirection.aboveRoot) {
     796             :       return _PathRelation.inconclusive;
     797             :     }
     798             : 
     799             :     // The child is within the parent if and only if we're on a separator
     800             :     // boundary.
     801             :     //
     802             :     //     isWithin("foo/bar", "foo/bar/baz") //=> true
     803             :     //     isWithin("foo/bar/", "foo/bar/baz") //=> true
     804             :     //     isWithin("foo/bar", "foo/barbaz") //=> false
     805           0 :     return (style.isSeparator(child.codeUnitAt(childIndex)) ||
     806           0 :             style.isSeparator(lastCodeUnit))
     807             :         ? _PathRelation.within
     808             :         : _PathRelation.different;
     809             :   }
     810             : 
     811             :   // Returns a [_PathDirection] describing the path represented by [codeUnits]
     812             :   // starting at [index].
     813             :   //
     814             :   // This ignores leading separators.
     815             :   //
     816             :   //     pathDirection("foo") //=> below root
     817             :   //     pathDirection("foo/bar/../baz") //=> below root
     818             :   //     pathDirection("//foo/bar/baz") //=> below root
     819             :   //     pathDirection("/") //=> at root
     820             :   //     pathDirection("foo/..") //=> at root
     821             :   //     pathDirection("foo/../baz") //=> reaches root
     822             :   //     pathDirection("foo/../..") //=> above root
     823             :   //     pathDirection("foo/../../foo/bar/baz") //=> above root
     824           0 :   _PathDirection _pathDirection(String path, int index) {
     825             :     var depth = 0;
     826             :     var reachedRoot = false;
     827             :     var i = index;
     828           0 :     while (i < path.length) {
     829             :       // Ignore initial separators or doubled separators.
     830           0 :       while (i < path.length && style.isSeparator(path.codeUnitAt(i))) {
     831           0 :         i++;
     832             :       }
     833             : 
     834             :       // If we're at the end, stop.
     835           0 :       if (i == path.length) break;
     836             : 
     837             :       // Move through the path component to the next separator.
     838             :       final start = i;
     839           0 :       while (i < path.length && !style.isSeparator(path.codeUnitAt(i))) {
     840           0 :         i++;
     841             :       }
     842             : 
     843             :       // See if the path component is ".", "..", or a name.
     844           0 :       if (i - start == 1 && path.codeUnitAt(start) == chars.period) {
     845             :         // Don't change the depth.
     846           0 :       } else if (i - start == 2 &&
     847           0 :           path.codeUnitAt(start) == chars.period &&
     848           0 :           path.codeUnitAt(start + 1) == chars.period) {
     849             :         // ".." backs out a directory.
     850           0 :         depth--;
     851             : 
     852             :         // If we work back beyond the root, stop.
     853           0 :         if (depth < 0) break;
     854             : 
     855             :         // Record that we reached the root so we don't return
     856             :         // [_PathDirection.belowRoot].
     857           0 :         if (depth == 0) reachedRoot = true;
     858             :       } else {
     859             :         // Step inside a directory.
     860           0 :         depth++;
     861             :       }
     862             : 
     863             :       // If we're at the end, stop.
     864           0 :       if (i == path.length) break;
     865             : 
     866             :       // Move past the separator.
     867           0 :       i++;
     868             :     }
     869             : 
     870           0 :     if (depth < 0) return _PathDirection.aboveRoot;
     871           0 :     if (depth == 0) return _PathDirection.atRoot;
     872             :     if (reachedRoot) return _PathDirection.reachesRoot;
     873             :     return _PathDirection.belowRoot;
     874             :   }
     875             : 
     876             :   /// Returns a hash code for [path] that matches the semantics of [equals].
     877             :   ///
     878             :   /// Note that the same path may have different hash codes in different
     879             :   /// [Context]s.
     880           0 :   int hash(String path) {
     881             :     // Make [path] absolute to ensure that equivalent relative and absolute
     882             :     // paths have the same hash code.
     883           0 :     path = absolute(path);
     884             : 
     885           0 :     final result = _hashFast(path);
     886             :     if (result != null) return result;
     887             : 
     888           0 :     final parsed = _parse(path);
     889           0 :     parsed.normalize();
     890           0 :     return _hashFast(parsed.toString())!;
     891             :   }
     892             : 
     893             :   /// An optimized implementation of [hash] that doesn't handle internal `..`
     894             :   /// components.
     895             :   ///
     896             :   /// This will handle `..` components that appear at the beginning of the path.
     897           0 :   int? _hashFast(String path) {
     898             :     var hash = 4603;
     899             :     var beginning = true;
     900             :     var wasSeparator = true;
     901           0 :     for (var i = 0; i < path.length; i++) {
     902           0 :       final codeUnit = style.canonicalizeCodeUnit(path.codeUnitAt(i));
     903             : 
     904             :       // Take advantage of the fact that collisions are allowed to ignore
     905             :       // separators entirely. This lets us avoid worrying about cases like
     906             :       // multiple trailing slashes.
     907           0 :       if (style.isSeparator(codeUnit)) {
     908             :         wasSeparator = true;
     909             :         continue;
     910             :       }
     911             : 
     912           0 :       if (codeUnit == chars.period && wasSeparator) {
     913             :         // If a dot comes after a separator, it may be a directory traversal
     914             :         // operator. To check that, we need to know if it's followed by either
     915             :         // "/" or "./". Otherwise, it's just a normal character.
     916             :         //
     917             :         //     hash("foo/./bar") == hash("foo/bar")
     918             : 
     919             :         // We've hit "/." at the end of the path, which we can ignore.
     920           0 :         if (i + 1 == path.length) break;
     921             : 
     922           0 :         final next = path.codeUnitAt(i + 1);
     923             : 
     924             :         // We can just ignore "/./", since they don't affect the semantics of
     925             :         // the path.
     926           0 :         if (style.isSeparator(next)) continue;
     927             : 
     928             :         // If the path ends with "/.." or contains "/../", we need to
     929             :         // canonicalize it before we can hash it. We make an exception for ".."s
     930             :         // at the beginning of the path, since those may appear even in a
     931             :         // canonicalized path.
     932             :         if (!beginning &&
     933           0 :             next == chars.period &&
     934           0 :             (i + 2 == path.length ||
     935           0 :                 style.isSeparator(path.codeUnitAt(i + 2)))) {
     936             :           return null;
     937             :         }
     938             :       }
     939             : 
     940             :       // Make sure [hash] stays under 32 bits even after multiplication.
     941           0 :       hash &= 0x3FFFFFF;
     942           0 :       hash *= 33;
     943           0 :       hash ^= codeUnit;
     944             :       wasSeparator = false;
     945             :       beginning = false;
     946             :     }
     947             :     return hash;
     948             :   }
     949             : 
     950             :   /// Removes a trailing extension from the last part of [path].
     951             :   ///
     952             :   ///     context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo'
     953           0 :   String withoutExtension(String path) {
     954           0 :     final parsed = _parse(path);
     955             : 
     956           0 :     for (var i = parsed.parts.length - 1; i >= 0; i--) {
     957           0 :       if (parsed.parts[i].isNotEmpty) {
     958           0 :         parsed.parts[i] = parsed.basenameWithoutExtension;
     959             :         break;
     960             :       }
     961             :     }
     962             : 
     963           0 :     return parsed.toString();
     964             :   }
     965             : 
     966             :   /// Returns [path] with the trailing extension set to [extension].
     967             :   ///
     968             :   /// If [path] doesn't have a trailing extension, this just adds [extension] to
     969             :   /// the end.
     970             :   ///
     971             :   ///     context.setExtension('path/to/foo.dart', '.js')
     972             :   ///       // -> 'path/to/foo.js'
     973             :   ///     context.setExtension('path/to/foo.dart.js', '.map')
     974             :   ///       // -> 'path/to/foo.dart.map'
     975             :   ///     context.setExtension('path/to/foo', '.js')
     976             :   ///       // -> 'path/to/foo.js'
     977           0 :   String setExtension(String path, String extension) =>
     978           0 :       withoutExtension(path) + extension;
     979             : 
     980             :   /// Returns the path represented by [uri], which may be a [String] or a [Uri].
     981             :   ///
     982             :   /// For POSIX and Windows styles, [uri] must be a `file:` URI. For the URL
     983             :   /// style, this will just convert [uri] to a string.
     984             :   ///
     985             :   ///     // POSIX
     986             :   ///     context.fromUri('file:///path/to/foo')
     987             :   ///       // -> '/path/to/foo'
     988             :   ///
     989             :   ///     // Windows
     990             :   ///     context.fromUri('file:///C:/path/to/foo')
     991             :   ///       // -> r'C:\path\to\foo'
     992             :   ///
     993             :   ///     // URL
     994             :   ///     context.fromUri('https://dart.dev/path/to/foo')
     995             :   ///       // -> 'https://dart.dev/path/to/foo'
     996             :   ///
     997             :   /// If [uri] is relative, a relative path will be returned.
     998             :   ///
     999             :   ///     path.fromUri('path/to/foo'); // -> 'path/to/foo'
    1000          44 :   String fromUri(uri) => style.pathFromUri(_parseUri(uri));
    1001             : 
    1002             :   /// Returns the URI that represents [path].
    1003             :   ///
    1004             :   /// For POSIX and Windows styles, this will return a `file:` URI. For the URL
    1005             :   /// style, this will just convert [path] to a [Uri].
    1006             :   ///
    1007             :   ///     // POSIX
    1008             :   ///     context.toUri('/path/to/foo')
    1009             :   ///       // -> Uri.parse('file:///path/to/foo')
    1010             :   ///
    1011             :   ///     // Windows
    1012             :   ///     context.toUri(r'C:\path\to\foo')
    1013             :   ///       // -> Uri.parse('file:///C:/path/to/foo')
    1014             :   ///
    1015             :   ///     // URL
    1016             :   ///     context.toUri('https://dart.dev/path/to/foo')
    1017             :   ///       // -> Uri.parse('https://dart.dev/path/to/foo')
    1018           0 :   Uri toUri(String path) {
    1019           0 :     if (isRelative(path)) {
    1020           0 :       return style.relativePathToUri(path);
    1021             :     } else {
    1022           0 :       return style.absolutePathToUri(join(current, path));
    1023             :     }
    1024             :   }
    1025             : 
    1026             :   /// Returns a terse, human-readable representation of [uri].
    1027             :   ///
    1028             :   /// [uri] can be a [String] or a [Uri]. If it can be made relative to the
    1029             :   /// current working directory, that's done. Otherwise, it's returned as-is.
    1030             :   /// This gracefully handles non-`file:` URIs for [Style.posix] and
    1031             :   /// [Style.windows].
    1032             :   ///
    1033             :   /// The returned value is meant for human consumption, and may be either URI-
    1034             :   /// or path-formatted.
    1035             :   ///
    1036             :   ///     // POSIX
    1037             :   ///     var context = Context(current: '/root/path');
    1038             :   ///     context.prettyUri('file:///root/path/a/b.dart'); // -> 'a/b.dart'
    1039             :   ///     context.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
    1040             :   ///
    1041             :   ///     // Windows
    1042             :   ///     var context = Context(current: r'C:\root\path');
    1043             :   ///     context.prettyUri('file:///C:/root/path/a/b.dart'); // -> r'a\b.dart'
    1044             :   ///     context.prettyUri('https://dart.dev/'); // -> 'https://dart.dev'
    1045             :   ///
    1046             :   ///     // URL
    1047             :   ///     var context = Context(current: 'https://dart.dev/root/path');
    1048             :   ///     context.prettyUri('https://dart.dev/root/path/a/b.dart');
    1049             :   ///         // -> r'a/b.dart'
    1050             :   ///     context.prettyUri('file:///root/path'); // -> 'file:///root/path'
    1051          11 :   String prettyUri(uri) {
    1052          11 :     final typedUri = _parseUri(uri);
    1053          55 :     if (typedUri.scheme == 'file' && style == Style.url) {
    1054           0 :       return typedUri.toString();
    1055          22 :     } else if (typedUri.scheme != 'file' &&
    1056          22 :         typedUri.scheme != '' &&
    1057          33 :         style != Style.url) {
    1058          11 :       return typedUri.toString();
    1059             :     }
    1060             : 
    1061          22 :     final path = normalize(fromUri(typedUri));
    1062          11 :     final rel = relative(path);
    1063             : 
    1064             :     // Only return a relative path if it's actually shorter than the absolute
    1065             :     // path. This avoids ugly things like long "../" chains to get to the root
    1066             :     // and then go back down.
    1067          55 :     return split(rel).length > split(path).length ? path : rel;
    1068             :   }
    1069             : 
    1070          33 :   ParsedPath _parse(String path) => ParsedPath.parse(path, style);
    1071             : }
    1072             : 
    1073             : /// Parses argument if it's a [String] or returns it intact if it's a [Uri].
    1074             : ///
    1075             : /// Throws an [ArgumentError] otherwise.
    1076          11 : Uri _parseUri(uri) {
    1077          11 :   if (uri is String) return Uri.parse(uri);
    1078          11 :   if (uri is Uri) return uri;
    1079           0 :   throw ArgumentError.value(uri, 'uri', 'Value must be a String or a Uri');
    1080             : }
    1081             : 
    1082             : /// Validates that there are no non-null arguments following a null one and
    1083             : /// throws an appropriate [ArgumentError] on failure.
    1084           0 : void _validateArgList(String method, List<String?> args) {
    1085           0 :   for (var i = 1; i < args.length; i++) {
    1086             :     // Ignore nulls hanging off the end.
    1087           0 :     if (args[i] == null || args[i - 1] != null) continue;
    1088             : 
    1089             :     int numArgs;
    1090           0 :     for (numArgs = args.length; numArgs >= 1; numArgs--) {
    1091           0 :       if (args[numArgs - 1] != null) break;
    1092             :     }
    1093             : 
    1094             :     // Show the arguments.
    1095           0 :     final message = StringBuffer();
    1096           0 :     message.write('$method(');
    1097           0 :     message.write(args
    1098           0 :         .take(numArgs)
    1099           0 :         .map((arg) => arg == null ? 'null' : '"$arg"')
    1100           0 :         .join(', '));
    1101           0 :     message.write('): part ${i - 1} was null, but part $i was not.');
    1102           0 :     throw ArgumentError(message.toString());
    1103             :   }
    1104             : }
    1105             : 
    1106             : /// An enum of possible return values for [Context._pathDirection].
    1107             : class _PathDirection {
    1108             :   /// The path contains enough ".." components that at some point it reaches
    1109             :   /// above its original root.
    1110             :   ///
    1111             :   /// Note that this applies even if the path ends beneath its original root. It
    1112             :   /// takes precendence over any other return values that may apple.
    1113             :   static const aboveRoot = _PathDirection('above root');
    1114             : 
    1115             :   /// The path contains enough ".." components that it ends at its original
    1116             :   /// root.
    1117             :   static const atRoot = _PathDirection('at root');
    1118             : 
    1119             :   /// The path contains enough ".." components that at some point it reaches its
    1120             :   /// original root, but it ends beneath that root.
    1121             :   static const reachesRoot = _PathDirection('reaches root');
    1122             : 
    1123             :   /// The path never reaches to or above its original root.
    1124             :   static const belowRoot = _PathDirection('below root');
    1125             : 
    1126             :   final String name;
    1127             : 
    1128          11 :   const _PathDirection(this.name);
    1129             : 
    1130           0 :   @override
    1131           0 :   String toString() => name;
    1132             : }
    1133             : 
    1134             : /// An enum of possible return values for [Context._isWithinOrEquals].
    1135             : class _PathRelation {
    1136             :   /// The first path is a proper parent of the second.
    1137             :   ///
    1138             :   /// For example, `foo` is a proper parent of `foo/bar`, but not of `foo`.
    1139             :   static const within = _PathRelation('within');
    1140             : 
    1141             :   /// The two paths are equivalent.
    1142             :   ///
    1143             :   /// For example, `foo//bar` is equivalent to `foo/bar`.
    1144             :   static const equal = _PathRelation('equal');
    1145             : 
    1146             :   /// The first path is neither a parent of nor equal to the second.
    1147             :   static const different = _PathRelation('different');
    1148             : 
    1149             :   /// We couldn't quickly determine any information about the paths'
    1150             :   /// relationship to each other.
    1151             :   ///
    1152             :   /// Only returned by [Context._isWithinOrEqualsFast].
    1153             :   static const inconclusive = _PathRelation('inconclusive');
    1154             : 
    1155             :   final String name;
    1156             : 
    1157          11 :   const _PathRelation(this.name);
    1158             : 
    1159           0 :   @override
    1160           0 :   String toString() => name;
    1161             : }

Generated by: LCOV version 1.14