paths.ts 10.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

J
Johannes Rieken 已提交
7 8 9
import { isLinux, isWindows } from 'vs/base/common/platform';
import { fill } from 'vs/base/common/arrays';
import { CharCode } from 'vs/base/common/charCode';
E
Erich Gamma 已提交
10 11 12 13

/**
 * The forward slash path separator.
 */
B
Benjamin Pasero 已提交
14
export const sep = '/';
E
Erich Gamma 已提交
15 16 17 18

/**
 * The native path separator depending on the OS.
 */
B
Benjamin Pasero 已提交
19
export const nativeSep = isWindows ? '\\' : '/';
E
Erich Gamma 已提交
20 21

export function relative(from: string, to: string): string {
22 23
	const originalNormalizedFrom = normalize(from);
	const originalNormalizedTo = normalize(to);
E
Erich Gamma 已提交
24

25 26 27 28
	// we're assuming here that any non=linux OS is case insensitive
	// so we must compare each part in its lowercase form
	const normalizedFrom = isLinux ? originalNormalizedFrom : originalNormalizedFrom.toLowerCase();
	const normalizedTo = isLinux ? originalNormalizedTo : originalNormalizedTo.toLowerCase();
E
Erich Gamma 已提交
29

30 31
	const fromParts = normalizedFrom.split(sep);
	const toParts = normalizedTo.split(sep);
E
Erich Gamma 已提交
32

33 34 35 36
	let i = 0, max = Math.min(fromParts.length, toParts.length);

	for (; i < max; i++) {
		if (fromParts[i] !== toParts[i]) {
E
Erich Gamma 已提交
37 38 39 40
			break;
		}
	}

41 42 43 44
	const result = [
		...fill(fromParts.length - i, () => '..'),
		...originalNormalizedTo.split(sep).slice(i)
	];
E
Erich Gamma 已提交
45

46
	return result.join(sep);
E
Erich Gamma 已提交
47 48
}

49 50 51 52
/**
 * @returns the directory name of a path.
 */
export function dirname(path: string): string {
B
Benjamin Pasero 已提交
53
	const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
54 55 56 57 58 59 60 61 62 63 64 65 66
	if (idx === 0) {
		return '.';
	} else if (~idx === 0) {
		return path[0];
	} else {
		return path.substring(0, ~idx);
	}
}

/**
 * @returns the base name of a path.
 */
export function basename(path: string): string {
B
Benjamin Pasero 已提交
67
	const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
68 69 70 71 72 73 74 75 76 77 78 79 80 81
	if (idx === 0) {
		return path;
	} else if (~idx === path.length - 1) {
		return basename(path.substring(0, path.length - 1));
	} else {
		return path.substr(~idx + 1);
	}
}

/**
 * @returns {{.far}} from boo.far or the empty string.
 */
export function extname(path: string): string {
	path = basename(path);
B
Benjamin Pasero 已提交
82
	const idx = ~path.lastIndexOf('.');
83 84 85
	return idx ? path.substring(~idx) : '';
}

J
Johannes Rieken 已提交
86 87
const _posixBadPath = /(\/\.\.?\/)|(\/\.\.?)$|^(\.\.?\/)|(\/\/+)|(\\)/;
const _winBadPath = /(\\\.\.?\\)|(\\\.\.?)$|^(\.\.?\\)|(\\\\+)|(\/)/;
J
Johannes Rieken 已提交
88

J
Johannes Rieken 已提交
89 90 91 92
function _isNormal(path: string, win: boolean): boolean {
	return win
		? !_winBadPath.test(path)
		: !_posixBadPath.test(path);
J
Johannes Rieken 已提交
93 94 95 96 97 98 99 100
}

export function normalize(path: string, toOSPath?: boolean): string {

	if (path === null || path === void 0) {
		return path;
	}

101
	const len = path.length;
J
Johannes Rieken 已提交
102 103 104 105
	if (len === 0) {
		return '.';
	}

106 107
	const wantsBackslash = isWindows && toOSPath;
	if (_isNormal(path, wantsBackslash)) {
J
Johannes Rieken 已提交
108 109
		return path;
	}
J
Johannes Rieken 已提交
110

111
	const sep = wantsBackslash ? '\\' : '/';
J
Johannes Rieken 已提交
112
	const root = getRoot(path, sep);
113

114 115 116
	// skip the root-portion of the path
	let start = root.length;
	let skip = false;
117
	let res = '';
118

119
	for (let end = root.length; end <= len; end++) {
120 121

		// either at the end or at a path-separator character
122
		if (end === len || path.charCodeAt(end) === CharCode.Slash || path.charCodeAt(end) === CharCode.Backslash) {
123

124
			if (streql(path, start, end, '..')) {
125 126 127
				// skip current and remove parent (if there is already something)
				let prev_start = res.lastIndexOf(sep);
				let prev_part = res.slice(prev_start + 1);
J
Johannes Rieken 已提交
128
				if ((root || prev_part.length > 0) && prev_part !== '..') {
129
					res = prev_start === -1 ? '' : res.slice(0, prev_start);
130
					skip = true;
131
				}
132 133 134
			} else if (streql(path, start, end, '.') && (root || res || end < len - 1)) {
				// skip current (if there is already something or if there is more to come)
				skip = true;
135 136
			}

137 138 139 140 141 142
			if (!skip) {
				let part = path.slice(start, end);
				if (res !== '' && res[res.length - 1] !== sep) {
					res += sep;
				}
				res += part;
143
			}
144
			start = end + 1;
145
			skip = false;
146 147 148
		}
	}

149
	return root + res;
150 151
}

152
function streql(value: string, start: number, end: number, other: string): boolean {
J
Johannes Rieken 已提交
153
	return start + other.length === end && value.indexOf(other, start) === start;
154 155
}

156
/**
157 158 159
 * Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
 * `getRoot('files:///files/path') === files:///`,
 * or `getRoot('\\server\shares\path') === \\server\shares\`
160
 */
161
export function getRoot(path: string, sep: string = '/'): string {
E
Erich Gamma 已提交
162 163

	if (!path) {
164
		return '';
E
Erich Gamma 已提交
165 166
	}

167 168
	let len = path.length;
	let code = path.charCodeAt(0);
169
	if (code === CharCode.Slash || code === CharCode.Backslash) {
170 171

		code = path.charCodeAt(1);
172
		if (code === CharCode.Slash || code === CharCode.Backslash) {
173 174
			// UNC candidate \\localhost\shares\ddd
			//               ^^^^^^^^^^^^^^^^^^^
175
			code = path.charCodeAt(2);
176
			if (code !== CharCode.Slash && code !== CharCode.Backslash) {
177 178
				let pos = 3;
				let start = pos;
179 180
				for (; pos < len; pos++) {
					code = path.charCodeAt(pos);
181
					if (code === CharCode.Slash || code === CharCode.Backslash) {
182 183 184 185
						break;
					}
				}
				code = path.charCodeAt(pos + 1);
186
				if (start !== pos && code !== CharCode.Slash && code !== CharCode.Backslash) {
187 188 189
					pos += 1;
					for (; pos < len; pos++) {
						code = path.charCodeAt(pos);
190
						if (code === CharCode.Slash || code === CharCode.Backslash) {
191 192
							return path.slice(0, pos + 1) // consume this separator
								.replace(/[\\/]/g, sep);
193
						}
194 195 196
					}
				}
			}
E
Erich Gamma 已提交
197 198
		}

199 200
		// /user/far
		// ^
201
		return sep;
202

203
	} else if ((code >= CharCode.A && code <= CharCode.Z) || (code >= CharCode.a && code <= CharCode.z)) {
204 205
		// check for windows drive letter c:\ or c:

206
		if (path.charCodeAt(1) === CharCode.Colon) {
207
			code = path.charCodeAt(2);
208
			if (code === CharCode.Slash || code === CharCode.Backslash) {
209 210
				// C:\fff
				// ^^^
211
				return path.slice(0, 2) + sep;
212 213 214
			} else {
				// C:
				// ^^
215
				return path.slice(0, 2);
216
			}
E
Erich Gamma 已提交
217 218 219
		}
	}

220 221 222 223 224 225 226 227
	// 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);
228
			if (code === CharCode.Slash || code === CharCode.Backslash) {
229
				return path.slice(0, pos + 1); // consume this separator
230 231
			}
		}
E
Erich Gamma 已提交
232 233
	}

234
	return '';
E
Erich Gamma 已提交
235 236
}

J
Johannes Rieken 已提交
237
export const join: (...parts: string[]) => string = function () {
238 239 240
	// Not using a function with var-args because of how TS compiles
	// them to JS - it would result in 2*n runtime cost instead
	// of 1*n, where n is parts.length.
J
Johannes Rieken 已提交
241

242
	let value = '';
J
Johannes Rieken 已提交
243 244
	for (let i = 0; i < arguments.length; i++) {
		let part = arguments[i];
245 246 247 248
		if (i > 0) {
			// add the separater between two parts unless
			// there already is one
			let last = value.charCodeAt(value.length - 1);
249
			if (last !== CharCode.Slash && last !== CharCode.Backslash) {
250
				let next = part.charCodeAt(0);
251
				if (next !== CharCode.Slash && next !== CharCode.Backslash) {
252 253 254 255

					value += sep;
				}
			}
E
Erich Gamma 已提交
256
		}
257
		value += part;
E
Erich Gamma 已提交
258 259
	}

260
	return normalize(value);
J
Johannes Rieken 已提交
261
};
E
Erich Gamma 已提交
262 263


J
Johannes Rieken 已提交
264 265 266 267 268 269 270
/**
 * 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.
 */
E
Erich Gamma 已提交
271
export function isUNC(path: string): boolean {
J
Johannes Rieken 已提交
272 273 274
	if (!isWindows) {
		// UNC is a windows concept
		return false;
E
Erich Gamma 已提交
275 276
	}

J
Johannes Rieken 已提交
277 278 279 280
	if (!path || path.length < 5) {
		// at least \\a\b
		return false;
	}
E
Erich Gamma 已提交
281

J
Johannes Rieken 已提交
282
	let code = path.charCodeAt(0);
283
	if (code !== CharCode.Backslash) {
J
Johannes Rieken 已提交
284 285 286
		return false;
	}
	code = path.charCodeAt(1);
287
	if (code !== CharCode.Backslash) {
J
Johannes Rieken 已提交
288 289 290 291 292 293
		return false;
	}
	let pos = 2;
	let start = pos;
	for (; pos < path.length; pos++) {
		code = path.charCodeAt(pos);
294
		if (code === CharCode.Backslash) {
J
Johannes Rieken 已提交
295 296 297 298 299 300 301
			break;
		}
	}
	if (start === pos) {
		return false;
	}
	code = path.charCodeAt(pos + 1);
302
	if (isNaN(code) || code === CharCode.Backslash) {
J
Johannes Rieken 已提交
303 304 305
		return false;
	}
	return true;
E
Erich Gamma 已提交
306 307
}

I
isidor 已提交
308
function isPosixAbsolute(path: string): boolean {
E
Erich Gamma 已提交
309 310 311
	return path && path[0] === '/';
}

312 313
export function makePosixAbsolute(path: string): string {
	return isPosixAbsolute(normalize(path)) ? path : sep + path;
E
Erich Gamma 已提交
314 315 316 317
}

export function isEqualOrParent(path: string, candidate: string): boolean {

318
	if (path === candidate) {
E
Erich Gamma 已提交
319 320 321 322 323 324 325 326
		return true;
	}

	path = normalize(path);
	candidate = normalize(candidate);

	let candidateLen = candidate.length;
	let lastCandidateChar = candidate.charCodeAt(candidateLen - 1);
327
	if (lastCandidateChar === CharCode.Slash) {
E
Erich Gamma 已提交
328 329 330 331
		candidate = candidate.substring(0, candidateLen - 1);
		candidateLen -= 1;
	}

332
	if (path === candidate) {
E
Erich Gamma 已提交
333 334 335
		return true;
	}

336
	if (!isLinux) {
E
Erich Gamma 已提交
337 338 339 340 341
		// case insensitive
		path = path.toLowerCase();
		candidate = candidate.toLowerCase();
	}

342
	if (path === candidate) {
E
Erich Gamma 已提交
343 344 345 346 347 348 349 350
		return true;
	}

	if (path.indexOf(candidate) !== 0) {
		return false;
	}

	let char = path.charCodeAt(candidateLen);
351
	return char === CharCode.Slash;
E
Erich Gamma 已提交
352
}
353 354 355

// Reference: https://en.wikipedia.org/wiki/Filename
const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
356
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
export function isValidBasename(name: string): boolean {
	if (!name || name.length === 0 || /^\s+$/.test(name)) {
		return false; // require a name that is not just whitespace
	}

	INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development
	if (INVALID_FILE_CHARS.test(name)) {
		return false; // check for certain invalid file characters
	}

	if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) {
		return false; // check for certain invalid file names
	}

	if (name === '.' || name === '..') {
		return false; // check for reserved values
	}

375
	if (isWindows && name[name.length - 1] === '.') {
376 377 378 379 380 381 382 383
		return false; // Windows: file cannot end with a "."
	}

	if (isWindows && name.length !== name.trim().length) {
		return false; // Windows: file cannot end with a whitespace
	}

	return true;
I
isidor 已提交
384 385 386 387
}

export const isAbsoluteRegex = /^((\/|[a-zA-Z]:\\)[^\(\)<>\\'\"\[\]]+)/;

I
isidor 已提交
388 389 390 391
/**
 * If you have access to node, it is recommended to use node's path.isAbsolute().
 * This is a simple regex based approach.
 */
I
isidor 已提交
392 393 394
export function isAbsolute(path: string): boolean {
	return isAbsoluteRegex.test(path);
}