blob: 28f5732cb94994f57155674b1e8e4eb1e0546ef0 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library analyzer.src.util.absolute_path;
/// The class for manipulating absolute, normalized paths.
class AbsolutePathContext {
static const int _COLON = 0x3A;
static const int _LOWER_A = 0x61;
static const int _LOWER_Z = 0x7A;
static const int _UPPER_A = 0x41;
static const int _UPPER_Z = 0x5A;
final bool _isWindows;
String separator;
int _separatorChar;
String _singlePeriodComponent;
String _doublePeriodComponent;
String _singlePeriodEnding;
String _doublePeriodEnding;
AbsolutePathContext(this._isWindows) {
separator = _isWindows ? r'\' : '/';
_separatorChar = separator.codeUnitAt(0);
_singlePeriodComponent = separator + '.' + separator;
_doublePeriodComponent = separator + '..' + separator;
_singlePeriodEnding = separator + '.';
_doublePeriodEnding = separator + '..';
}
/// Append the given relative [suffix] to the given absolute [parent].
///
/// context.append('/path/to', 'foo'); // -> '/path/to/foo'
///
/// The given [suffix] cannot be an absolute path or use `..`.
String append(String parent, String suffix) {
return '$parent$separator$suffix';
}
/// Return the part of the absolute [path] after the last separator on the
/// context's platform.
///
/// context.basename('/path/to/foo.dart'); // -> 'foo.dart'
/// context.basename('/path/to'); // -> 'to'
/// context.basename('/path'); // -> 'path'
/// context.basename('/'); // -> ''
String basename(String path) {
int index = path.lastIndexOf(separator);
return path.substring(index + 1);
}
/// Return the part of the absolute [path] before the last separator.
///
/// context.dirname('/path/to/foo.dart'); // -> '/path/to'
/// context.dirname('/path/to'); // -> '/path'
/// context.dirname(r'/path'); // -> '/'
/// context.dirname(r'/'); // -> '/'
/// context.dirname(r'C:\path'); // -> 'C:\'
/// context.dirname(r'C:\'); // -> 'C:\'
String dirname(String path) {
int firstIndex = path.indexOf(separator);
int lastIndex = path.lastIndexOf(separator);
return lastIndex == firstIndex
? path.substring(0, firstIndex + 1)
: path.substring(0, lastIndex);
}
/// Return `true` if the given [path] is valid.
///
/// context.isNormalized('/foo/bar'); // -> true
/// context.isNormalized('/foo/bar/../baz'); // -> false
bool isValid(String path) {
return _isAbsolute(path) && _isNormalized(path);
}
/// Return `true` if [child] is a path beneath [parent], and `false`
/// otherwise. Both the [child] and [parent] paths must be absolute paths.
///
/// context.isWithin('/root/path', '/root/path/a'); // -> true
/// context.isWithin('/root/path', '/root/other'); // -> false
/// context.isWithin('/root/path', '/root/path'); // -> false
bool isWithin(String parent, String child) {
int parentLength = parent.length;
int childLength = child.length;
if (parentLength >= childLength) {
return false;
}
if (child.codeUnitAt(parentLength) != _separatorChar) {
return false;
}
return _startsWithUnsafe(child, parent);
}
/// Split [path] into its components using [separator].
///
/// context.split('/path/to/foo'); // -> ['', 'path', 'to', 'foo']
List<String> split(String path) {
return path.split(separator);
}
/// If the given [child] is within the given [parent], then return the
/// relative path from [parent] to [child]. Otherwise return `null`. Both
/// the [child] and [parent] paths must be absolute paths.
///
/// context.relative('/root/path', '/root/path/a/b.dart'); // -> 'a/b.dart'
/// context.relative('/root/path', '/root/other.dart'); // -> null
String suffix(String parent, String child) {
String parentPrefix = parent + separator;
if (child.startsWith(parentPrefix)) {
return child.substring(parentPrefix.length);
}
return null;
}
/// Return `true` if the given [path] is absolute.
///
/// _isAbsolute('/foo/bar'); // -> true
/// _isAbsolute('/'); // -> true
/// _isAbsolute('foo/bar'); // -> false
/// _isAbsolute('C:\foo\bar'); // -> true
/// _isAbsolute('C:\'); // -> true
/// _isAbsolute('foo\bar'); // -> false
bool _isAbsolute(String path) {
if (_isWindows) {
return path.length >= 3 &&
_isAlphabetic(path.codeUnitAt(0)) &&
path.codeUnitAt(1) == _COLON &&
path.codeUnitAt(2) == _separatorChar;
} else {
return path.isNotEmpty && path.codeUnitAt(0) == _separatorChar;
}
}
/// Return `true` if the given absolute [path] is normalized.
///
/// _isNormalized('/foo/bar'); // -> true
/// _isNormalized('/foo/..bar'); // -> true
/// _isNormalized('/foo/bar..'); // -> true
/// _isNormalized('/'); // -> true
/// _isNormalized('/foo/bar/../baz'); // -> false
/// _isNormalized('/foo/bar/..'); // -> false
bool _isNormalized(String path) {
return !path.contains(_singlePeriodComponent) &&
!path.contains(_doublePeriodComponent) &&
!path.endsWith(_singlePeriodEnding) &&
!path.endsWith(_doublePeriodEnding);
}
/// Returns whether [char] is the code for an ASCII letter (uppercase or
/// lowercase).
static bool _isAlphabetic(int char) {
return char >= _UPPER_A && char <= _UPPER_Z ||
char >= _LOWER_A && char <= _LOWER_Z;
}
/// Return `true` if [str] starts with the given [prefix].
///
/// The check is done from the end of [prefix], because absolute paths
/// usually have the same prefix, e.g. the user's home directory.
static bool _startsWithUnsafe(String str, String prefix) {
int len = prefix.length;
for (int i = len - 1; i >= 0; i--) {
if (str.codeUnitAt(i) != prefix.codeUnitAt(i)) {
return false;
}
}
return true;
}
}