提交 1635f5f6 编写于 作者: J Johannes Rieken

Merge pull request #7030 from Microsoft/joh/paths

Joh/paths
......@@ -5,7 +5,6 @@
'use strict';
import {isLinux, isWindows} from 'vs/base/common/platform';
import {endsWith} from 'vs/base/common/strings';
/**
* The forward slash path separator.
......@@ -41,38 +40,55 @@ export function relative(from: string, to: string): string {
return toParts.join(sep);
}
const _dotSegment = /[\\\/]\.\.?[\\\/]?|[\\\/]?\.\.?[\\\/]/;
export function normalize(path: string, toOSPath?: boolean): string {
if (!path) {
if (path === null || path === void 0) {
return path;
}
// a path is already normal if it contains no .. or . parts
// and already uses the proper path separator
if (!_dotSegment.test(path)) {
// badSep is the path separator we don't want. Usually
// the backslash, unless isWindows && toOSPath
let badSep = toOSPath && isWindows ? '/' : '\\';
if (path.indexOf(badSep) === -1) {
return path;
}
let len = path.length;
if (len === 0) {
return '.';
}
let parts = path.split(/[\\\/]/);
for (let i = 0, len = parts.length; i < len; i++) {
if (parts[i] === '.' && (parts[i + 1] || parts[i - 1])) {
parts.splice(i, 1);
i -= 1;
} else if (parts[i] === '..' && !!parts[i - 1]) {
parts.splice(i - 1, 2);
i -= 2;
const sep = isWindows && toOSPath ? '\\' : '/';
const root = getRoot(path, sep);
// operate on the 'path-portion' only
path = path.slice(root.length);
let res = '';
let start = 0;
for (let end = 0; end <= len; end++) {
let code = path.charCodeAt(end);
if (code === _slash || code === _backslash || end === len) {
let part = path.slice(start, end);
start = end + 1;
if (part === '.' && (root || res || end < len - 1)) {
// skip current (if there is already something or if there is more to come)
continue;
}
if (part === '..') {
// skip current and remove parent (if there is already something)
let prev_start = res.lastIndexOf(sep);
let prev_part = res.slice(prev_start + 1);
if ((root || prev_part.length > 0) && prev_part !== '..') {
res = prev_start === -1 ? '' : res.slice(0, prev_start);
continue;
}
}
if (res !== '' && res[res.length - 1] !== sep) {
res += sep;
}
res += part;
}
}
return parts.join(toOSPath ? nativeSep : sep);
return root + res;
}
/**
......@@ -112,93 +128,153 @@ export function extname(path: string): string {
return idx ? path.substring(~idx) : '';
}
function getRootLength(path: string): number {
/**
* Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
* `getRoot('files:///files/path') === files:///`,
* or `getRoot('\\server\shares\path') === \\server\shares\`
*/
export function getRoot(path: string, sep: string = '/'): string {
if (!path) {
return 0;
return '';
}
path = path.replace(/\/|\\/g, '/');
if (path[0] === '/') {
if (path[1] !== '/') {
// /far/boo
return 1;
} else {
// //server/far/boo
return 2;
let len = path.length;
let code = path.charCodeAt(0);
if (code === _slash || code === _backslash) {
code = path.charCodeAt(1);
if (code === _slash || code === _backslash) {
// UNC candidate \\localhost\shares\ddd
// ^^^^^^^^^^^^^^^^^^^
code = path.charCodeAt(2);
if (code !== _slash && code !== _backslash) {
let pos = 3;
let start = pos;
for (; pos < len; pos++) {
code = path.charCodeAt(pos);
if (code === _slash || code === _backslash) {
break;
}
}
code = path.charCodeAt(pos + 1);
if (start !== pos && code !== _slash && code !== _backslash) {
pos += 1;
for (; pos < len; pos++) {
code = path.charCodeAt(pos);
if (code === _slash || code === _backslash) {
return path.slice(0, pos + 1) // consume this separator
.replace(/[\\/]/g, sep);
}
}
}
}
}
}
if (path[1] === ':') {
if (path[2] === '/') {
// c:/boo/far.txt
return 3;
} else {
// c:
return 2;
// /user/far
// ^
return sep;
} else if ((code >= _A && code <= _Z) || (code >= _a && code <= _z)) {
// check for windows drive letter c:\ or c:
if (path.charCodeAt(1) === _colon) {
code = path.charCodeAt(2);
if (code === _slash || code === _backslash) {
// C:\fff
// ^^^
return path.slice(0, 2) + sep;
} else {
// C:
// ^^
return path.slice(0, 2);
}
}
}
if (path.indexOf('file:///') === 0) {
return 8; // 8 -> 'file:///'.length
// check for URI
// scheme://authority/path
// ^^^^^^^^^^^^^^^^^^^
let pos = path.indexOf('://');
if (pos !== -1) {
pos += 3; // 3 -> "://".length
for (; pos < len; pos++) {
code = path.charCodeAt(pos);
if (code === _slash || code === _backslash) {
return path.slice(0, pos + 1); // consume this separator
}
}
}
var idx = path.indexOf('://');
if (idx !== -1) {
return idx + 3; // 3 -> "://".length
}
return 0;
return '';
}
export function join(...parts: string[]): string {
var rootLen = getRootLength(parts[0]),
root: string;
export const join: (...parts: string[]) => string = function () {
let value = '';
for (let i = 0; i < arguments.length; i++) {
let part = arguments[i];
if (i > 0) {
// add the separater between two parts unless
// there already is one
let last = value.charCodeAt(value.length - 1);
if (last !== _slash && last !== _backslash) {
let next = part.charCodeAt(0);
if (next !== _slash && next !== _backslash) {
value += sep;
}
}
}
value += part;
}
// simply preserve things like c:/, //localhost/, file:///, http://, etc
root = parts[0].substr(0, rootLen);
parts[0] = parts[0].substr(rootLen);
return normalize(value);
};
var allParts: string[] = [],
endsWithSep = /[\\\/]$/.test(parts[parts.length - 1]);
for (var i = 0; i < parts.length; i++) {
allParts.push.apply(allParts, parts[i].split(/\/|\\/));
/**
* Check if the path follows this pattern: `\\hostname\sharename`.
*
* @see https://msdn.microsoft.com/en-us/library/gg465305.aspx
* @return A boolean indication if the path is a UNC path, on none-windows
* always false.
*/
export function isUNC(path: string): boolean {
if (!isWindows) {
// UNC is a windows concept
return false;
}
for (var i = 0; i < allParts.length; i++) {
var part = allParts[i];
if (part === '.' || part.length === 0) {
allParts.splice(i, 1);
i -= 1;
} else if (part === '..' && !!allParts[i - 1] && allParts[i - 1] !== '..') {
allParts.splice(i - 1, 2);
i -= 2;
}
if (!path || path.length < 5) {
// at least \\a\b
return false;
}
if (endsWithSep) {
allParts.push('');
let code = path.charCodeAt(0);
if (code !== _backslash) {
return false;
}
var ret = allParts.join('/');
if (root) {
ret = root.replace(/\/|\\/g, '/') + ret;
code = path.charCodeAt(1);
if (code !== _backslash) {
return false;
}
return ret;
}
export function isUNC(path: string): boolean {
if (!isWindows || !path) {
return false; // UNC is a windows concept
let pos = 2;
let start = pos;
for (; pos < path.length; pos++) {
code = path.charCodeAt(pos);
if (code === _backslash) {
break;
}
}
path = this.normalize(path, true);
return path[0] === nativeSep && path[1] === nativeSep;
if (start === pos) {
return false;
}
code = path.charCodeAt(pos + 1);
if (isNaN(code) || code === _backslash) {
return false;
}
return true;
}
function isPosixAbsolute(path: string): boolean {
......@@ -214,6 +290,12 @@ export function isRelative(path: string): boolean {
}
const _slash = '/'.charCodeAt(0);
const _backslash = '\\'.charCodeAt(0);
const _colon = ':'.charCodeAt(0);
const _a = 'a'.charCodeAt(0);
const _A = 'A'.charCodeAt(0);
const _z = 'z'.charCodeAt(0);
const _Z = 'Z'.charCodeAt(0);
export function isEqualOrParent(path: string, candidate: string): boolean {
......@@ -274,7 +356,7 @@ export function isValidBasename(name: string): boolean {
return false; // check for reserved values
}
if (isWindows && endsWith(name, '.')) {
if (isWindows && name[name.length - 1] === '.') {
return false; // Windows: file cannot end with a "."
}
......
......@@ -28,29 +28,63 @@ suite('Paths', () => {
});
test('normalize', () => {
assert.equal(paths.normalize(''), '.');
assert.equal(paths.normalize('.'), '.');
assert.equal(paths.normalize('/'), '/');
assert.equal(paths.normalize('.'), '.');
assert.equal(paths.normalize('../../far'), '../../far');
assert.equal(paths.normalize('../bar'), '../bar');
assert.equal(paths.normalize('../far'), '../far');
assert.equal(paths.normalize('./'), './');
assert.equal(paths.normalize('./././'), './');
assert.equal(paths.normalize('./ff/./'), 'ff/');
assert.equal(paths.normalize('foo/./'), 'foo/');
// assert.equal(paths.normalize('//'), '/');
assert.equal(paths.normalize('./foo'), 'foo');
assert.equal(paths.normalize('/'), '/');
assert.equal(paths.normalize('/..'), '/');
assert.equal(paths.normalize('///'), '/');
assert.equal(paths.normalize('//foo'), '/foo');
assert.equal(paths.normalize('//foo//'), '/foo/');
assert.equal(paths.normalize('/foo'), '/foo');
assert.equal(paths.normalize('/foo/bar.test'), '/foo/bar.test');
assert.equal(paths.normalize('\\\\\\'), '/');
assert.equal(paths.normalize('c:/../ff'), 'c:/ff');
assert.equal(paths.normalize('c:\\./'), 'c:/');
assert.equal(paths.normalize('foo/'), 'foo/');
// assert.equal(paths.normalize('foo//'), 'foo/');
assert.equal(paths.normalize('foo\\bar'), 'foo/bar');
assert.equal(paths.normalize('foo/../../bar'), '../bar');
assert.equal(paths.normalize('foo/./'), 'foo/');
assert.equal(paths.normalize('foo/./bar'), 'foo/bar');
assert.equal(paths.normalize('foo/xxx/./bar'), 'foo/xxx/bar');
assert.equal(paths.normalize('foo/xxx/./../bar'), 'foo/bar');
assert.equal(paths.normalize('../bar'), '../bar');
assert.equal(paths.normalize('foo/xxx/./..'), 'foo');
assert.equal(paths.normalize('foo//'), 'foo/');
assert.equal(paths.normalize('foo//'), 'foo/');
assert.equal(paths.normalize('foo//bar'), 'foo/bar');
assert.equal(paths.normalize('foo//bar/far'), 'foo/bar/far');
assert.equal(paths.normalize('foo/bar/../../far'), 'far');
assert.equal(paths.normalize('foo/bar/../far'), 'foo/far');
assert.equal(paths.normalize('foo/far/../../bar'), 'bar');
assert.equal(paths.normalize('foo/far/../../bar'), 'bar');
assert.equal(paths.normalize('foo/xxx/..'), 'foo');
assert.equal(paths.normalize('foo/xxx/../bar'), 'foo/bar');
assert.equal(paths.normalize('foo/../../bar'), '../bar');
assert.equal(paths.normalize('foo/far/../../bar'), 'bar');
assert.equal(paths.normalize('foo/xxx/./..'), 'foo');
assert.equal(paths.normalize('foo/xxx/./../bar'), 'foo/bar');
assert.equal(paths.normalize('foo/xxx/./bar'), 'foo/xxx/bar');
assert.equal(paths.normalize('foo\\bar'), 'foo/bar');
assert.equal(paths.normalize(null), null);
assert.equal(paths.normalize(undefined), undefined);
});
test('getRootLength', () => {
assert.equal(paths.getRoot('/user/far'), '/');
assert.equal(paths.getRoot('\\\\server\\share\\some\\path'), '//server/share/');
assert.equal(paths.getRoot('//server/share/some/path'), '//server/share/');
assert.equal(paths.getRoot('//server/share'), '/');
assert.equal(paths.getRoot('//server'), '/');
assert.equal(paths.getRoot('//server//'), '/');
assert.equal(paths.getRoot('c:/user/far'), 'c:/');
assert.equal(paths.getRoot('c:user/far'), 'c:');
assert.equal(paths.getRoot('http://www'), '');
assert.equal(paths.getRoot('http://www/'), 'http://www/');
assert.equal(paths.getRoot('file:///foo'), 'file:///');
assert.equal(paths.getRoot('file://foo'), '');
// return input if already normal
assert.equal(paths.normalize('/foo/bar.test'), '/foo/bar.test');
});
test('makeAbsolute', () => {
......@@ -79,28 +113,30 @@ suite('Paths', () => {
});
test('join', () => {
assert.equal(paths.join('foo', 'bar'), 'foo/bar');
assert.equal(paths.join('foo/bar', './bar/foo'), 'foo/bar/bar/foo');
assert.equal(paths.join('foo/bar', '../bar/foo'), 'foo/bar/foo');
assert.equal(paths.join('../foo/bar', '../bar/foo'), '../foo/bar/foo');
assert.equal(paths.join('../../foo/bar', '../bar/foo'), '../../foo/bar/foo');
assert.equal(paths.join('.', 'bar'), 'bar');
assert.equal(paths.join('../../foo/bar', '../../foo'), '../../foo');
assert.equal(paths.join('../../foo/bar', '../bar/foo'), '../../foo/bar/foo');
assert.equal(paths.join('../foo/bar', '../bar/foo'), '../foo/bar/foo');
assert.equal(paths.join('/', 'bar'), '/bar');
assert.equal(paths.join('.', 'bar'), 'bar');
assert.equal(paths.join('http://localhost/test', 'test'), 'http://localhost/test/test');
assert.equal(paths.join('//server/far/boo', '../file.txt'), '//server/far/file.txt');
assert.equal(paths.join('/foo/', '/bar'), '/foo/bar');
assert.equal(paths.join('\\\\server\\far\\boo', '../file.txt'), '//server/far/file.txt');
assert.equal(paths.join('\\\\server\\far\\boo', './file.txt'), '//server/far/boo/file.txt');
assert.equal(paths.join('\\\\server\\far\\boo', '.\\file.txt'), '//server/far/boo/file.txt');
assert.equal(paths.join('\\\\server\\far\\boo', 'file.txt'), '//server/far/boo/file.txt');
assert.equal(paths.join('file:///c/users/test', 'test'), 'file:///c/users/test/test');
assert.equal(paths.join('file://localhost/c$/GitDevelopment/express', './settings'), 'file://localhost/c$/GitDevelopment/express/settings'); // unc
assert.equal(paths.join('file://localhost/c$/GitDevelopment/express', '.settings'), 'file://localhost/c$/GitDevelopment/express/.settings'); // unc
assert.equal(paths.join('foo/', 'bar'), 'foo/bar');
assert.equal(paths.join('foo', '/bar'), 'foo/bar');
assert.equal(paths.join('foo', 'bar'), 'foo/bar');
assert.equal(paths.join('foo', 'bar/'), 'foo/bar/');
assert.equal(paths.join('foo/', '/bar'), 'foo/bar');
assert.equal(paths.join('/foo/', '/bar'), '/foo/bar');
assert.equal(paths.join('foo/', '/bar/'), 'foo/bar/');
assert.equal(paths.join('foo', 'bar/'), 'foo/bar/');
assert.equal(paths.join('\\\\server\\far\\boo', 'file.txt'), '//server/far/boo/file.txt');
assert.equal(paths.join('\\\\server\\far\\boo', './file.txt'), '//server/far/boo/file.txt');
assert.equal(paths.join('\\\\server\\far\\boo', '.\\file.txt'), '//server/far/boo/file.txt');
assert.equal(paths.join('\\\\server\\far\\boo', '../file.txt'), '//server/far/file.txt');
assert.equal(paths.join('//server/far/boo', '../file.txt'), '//server/far/file.txt');
assert.equal(paths.join('foo/', 'bar'), 'foo/bar');
assert.equal(paths.join('foo/bar', '../bar/foo'), 'foo/bar/foo');
assert.equal(paths.join('foo/bar', './bar/foo'), 'foo/bar/bar/foo');
assert.equal(paths.join('http://localhost/test', '../next'), 'http://localhost/next');
assert.equal(paths.join('http://localhost/test', 'test'), 'http://localhost/test/test');
});
test('isEqualOrParent', () => {
......@@ -138,14 +174,16 @@ suite('Paths', () => {
});
test('isUNC', () => {
assert(!paths.isUNC('foo'));
assert(!paths.isUNC('/foo'));
assert(!paths.isUNC('\\foo'));
if (platform.isWindows) {
assert(paths.isUNC('\\\\foo'));
} else {
assert(!paths.isUNC('\\\\foo'));
assert.ok(!paths.isUNC('foo'));
assert.ok(!paths.isUNC('/foo'));
assert.ok(!paths.isUNC('\\foo'));
assert.ok(!paths.isUNC('\\\\foo'));
assert.ok(paths.isUNC('\\\\a\\b'));
assert.ok(!paths.isUNC('//a/b'));
assert.ok(paths.isUNC('\\\\server\\share'));
assert.ok(paths.isUNC('\\\\server\\share\\'));
assert.ok(paths.isUNC('\\\\server\\share\\path'));
}
});
......
......@@ -178,21 +178,6 @@ suite('URI', () => {
assert.throws(() => URI.parse('file:////shares/files/p.cs'));
});
// Useful reference:
test('correctFileUriToFilePath', () => {
var test = (input: string, expected: string) => {
expected = normalize(expected, true);
assert.equal(URI.parse(input).fsPath, expected, 'Result for ' + input);
};
test('file:///c:/alex.txt', 'c:\\alex.txt');
test('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins',
'c:\\Source\\Zürich or Zurich (ˈzjʊərɪk,\\Code\\resources\\app\\plugins');
test('file://monacotools/isi.txt', '\\\\monacotools\\isi.txt');
test('file://monacotools1/certificates/SSL/', '\\\\monacotools1\\certificates\\SSL\\');
});
test('URI#file', () => {
var value = URI.file('\\\\shäres\\path\\c#\\plugin.json');
......@@ -307,9 +292,8 @@ suite('URI', () => {
};
test('file:///c:/alex.txt', 'c:\\alex.txt');
test('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins',
'c:\\Source\\Zürich or Zurich (ˈzjʊərɪk,\\Code\\resources\\app\\plugins');
test('file://monacotools/isi.txt', '\\\\monacotools\\isi.txt');
test('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins', 'c:\\Source\\Zürich or Zurich (ˈzjʊərɪk,\\Code\\resources\\app\\plugins');
test('file://monacotools/folder/isi.txt', '\\\\monacotools\\folder\\isi.txt');
test('file://monacotools1/certificates/SSL/', '\\\\monacotools1\\certificates\\SSL\\');
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册