diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/paths.ts index e9cc91479cd83dba45f14614f9a5c4482d3f9211..45116f777edb7c4f1276dd55f4df6d2c8084fab3 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/paths.ts @@ -40,42 +40,42 @@ export function relative(from: string, to: string): string { return toParts.join(sep); } -const _dotSegment = /[\\\/]\.\.?[\\\/]?|[\\\/]?\.\.?[\\\/]/; +// const _dotSegment = /[\\\/]\.\.?[\\\/]?|[\\\/]?\.\.?[\\\/]/; + +// export function normalize(path: string, toOSPath?: boolean): string { + +// if (!path) { +// 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 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; +// } +// } + +// return parts.join(toOSPath ? nativeSep : sep); +// } export function normalize(path: string, toOSPath?: boolean): string { - if (!path) { - 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 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; - } - } - - return parts.join(toOSPath ? nativeSep : sep); -} - -export function normalize2(path: string, toOSPath?: boolean): string { - if (path === null || path === void 0) { return path; } @@ -85,14 +85,12 @@ export function normalize2(path: string, toOSPath?: boolean): string { return '.'; } - let rootLen = getRootLength(path); - let head = path.slice(0, rootLen); - let tail = ''; + const sep = isWindows && toOSPath ? '\\' : '/'; + const root = getRoot(path, sep); // operate on the 'path-portion' only - path = path.slice(rootLen); - - let sep = isWindows && toOSPath ? '\\' : '/'; + path = path.slice(root.length); + let res = ''; let start = 0; for (let end = 0; end <= len; end++) { @@ -102,26 +100,29 @@ export function normalize2(path: string, toOSPath?: boolean): string { 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 applicable) - let prev_start = tail.lastIndexOf(sep); - let prev_part = tail.slice(prev_start + 1); + // 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 (prev_part.length > 0 && prev_part !== '..') { - tail = prev_start === -1 ? '' : tail.slice(0, prev_start); + res = prev_start === -1 ? '' : res.slice(0, prev_start); continue; } } - if (tail !== '' && tail[tail.length - 1] !== sep) { - tail += sep; + if (res !== '' && res[res.length - 1] !== sep) { + res += sep; } - tail += part; + res += part; } } - // res += path.slice(start); - - return head + tail; + return root + res; } /** @@ -161,14 +162,21 @@ export function extname(path: string): string { return idx ? path.substring(~idx) : ''; } +enum PathType { + Unc, // \\server\shares\somepath + Uri, // scheme://authority/somepath + Drive, // windows drive letter path + Path // posix path OR windows current drive root relative +} + /** * Return the length of the denoting part of this path, like `c:\files === 3 (c:\)`, * `files:///files/path === 8 (files:///)`, or `\\server\shares\path === 16 (\\server\shares\)` */ -export function getRootLength(path: string): number { +export function getRoot(path: string, sep: string = '/'): string { if (!path) { - return 0; + return ''; } let len = path.length; @@ -179,7 +187,6 @@ export function getRootLength(path: string): number { if (code === _slash || code === _backslash) { // UNC candidate \\localhost\shares\ddd // ^^^^^^^^^^^^^^^^^^^ - code = path.charCodeAt(2); if (code !== _slash && code !== _backslash) { let pos = 3; @@ -196,7 +203,7 @@ export function getRootLength(path: string): number { for (; pos < len; pos++) { code = path.charCodeAt(pos); if (code === _slash || code === _backslash) { - return pos + 1; // consume this separator + return path.slice(0, pos + 1).replace(/[\\/]/g, sep); // consume this separator } } } @@ -205,7 +212,7 @@ export function getRootLength(path: string): number { // /user/far // ^ - return 1; + return sep; } else if ((code >= _A && code <= _Z) || (code >= _a && code <= _z)) { // check for windows drive letter c:\ or c: @@ -215,11 +222,11 @@ export function getRootLength(path: string): number { if (code === _slash || code === _backslash) { // C:\fff // ^^^ - return 3; + return path.slice(0, 2) + sep; } else { // C: // ^^ - return 2; + return path.slice(0, 2); } } } @@ -233,17 +240,17 @@ export function getRootLength(path: string): number { for (; pos < len; pos++) { code = path.charCodeAt(pos); if (code === _slash || code === _backslash) { - return pos + 1; // consume this separator + return path.slice(0, pos + 1); // consume this separator } } } - return 0; + return ''; } export function join(...parts: string[]): string { - var rootLen = getRootLength(parts[0]), + var rootLen = getRoot(parts[0]).length, root: string; // simply preserve things like c:/, //localhost/, file:///, http://, etc diff --git a/src/vs/base/test/common/paths.test.ts b/src/vs/base/test/common/paths.test.ts index 7785486a1dbbed6c6e915662e2cb8ca5a05825b8..a8a13144ae32a474b747c646658c3c2b19790912 100644 --- a/src/vs/base/test/common/paths.test.ts +++ b/src/vs/base/test/common/paths.test.ts @@ -28,68 +28,61 @@ 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('//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'); - - // return input if already normal - assert.equal(paths.normalize('/foo/bar.test'), '/foo/bar.test'); - }); - - test('normalize2', () => { - assert.equal(paths.normalize2('foo/bar/../far'), 'foo/far'); - assert.equal(paths.normalize2('foo/bar/../../far'), 'far'); - assert.equal(paths.normalize2('../far'), '../far'); - assert.equal(paths.normalize2('../../far'), '../../far'); - assert.equal(paths.normalize2('foo/xxx/..'), 'foo'); - assert.equal(paths.normalize2('foo/xxx/../bar'), 'foo/bar'); - assert.equal(paths.normalize2('foo/../../bar'), '../bar'); - assert.equal(paths.normalize2('foo/far/../../bar'), 'bar'); - assert.equal(paths.normalize2(undefined), undefined); - assert.equal(paths.normalize2(null), null); - assert.equal(paths.normalize2(''), '.'); - assert.equal(paths.normalize2('.'), '.'); - assert.equal(paths.normalize2('foo/'), 'foo/'); - assert.equal(paths.normalize2('foo//'), 'foo/'); - assert.equal(paths.normalize2('//foo'), '/foo'); - assert.equal(paths.normalize2('//foo//'), '/foo/'); - assert.equal(paths.normalize2('foo//bar'), 'foo/bar'); - assert.equal(paths.normalize2('foo//bar/far'), 'foo/bar/far'); - assert.equal(paths.normalize2('///'), '/'); - assert.equal(paths.normalize2('\\\\\\'), '\\'); + 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.getRootLength('/user/far'), 1); - assert.equal(paths.getRootLength('\\\\server\\share\\some\\path'), 15); - assert.equal(paths.getRootLength('//server/share/some/path'), 15); - assert.equal(paths.getRootLength('//server/share'), 1); - assert.equal(paths.getRootLength('//server'), 1); - assert.equal(paths.getRootLength('//server//'), 1); - assert.equal(paths.getRootLength('c:/user/far'), 3); - assert.equal(paths.getRootLength('c:user/far'), 2); - assert.equal(paths.getRootLength('http://wwww'), 0); - assert.equal(paths.getRootLength('http://wwww/'), 12); - assert.equal(paths.getRootLength('file:///foo'), 8); - assert.equal(paths.getRootLength('file://foo'), 0); + 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'), ''); }); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index deecc23d290bdd66ec5dbe57e4d9fbab38c4a90e..ad73e49c254c716e9103503675db241adefc716e 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -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\\'); });