src/vs/base/common/extpath.ts

433 LOC · 349 covered · 84 uncovered · 76 ranges · 13983 concepts · 34 introducers · 7335 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extpath.ts ×17
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CharCode } from './charCode.js';
7 > import { isAbsolute, join, normalize, posix, sep } from './path.js';
8 > import { isWindows } from './platform.js';
9 > import { equalsIgnoreCase, rtrim, startsWithIgnoreCase } from './strings.js';
10 > import { isNumber } from './types.js';
11 >
12 > export function isPathSeparator(code: number) {
13 > return code === CharCode.Slash || code === CharCode.Backslash; extpath.ts ×4
14 > }
16 > /**
17 > * Takes a Windows OS path and changes backward slashes to forward slashes.
18 > * This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
19 > * Using it on a Linux or MaxOS path might change it.
20 > */
21 > export function toSlashes(osPath: string) {
22 > return osPath.replace(/[\\/]/g, posix.sep); extpath.ts ×1
23 > }
25 > /**
26 > * Takes a Windows OS path (using backward or forward slashes) and turns it into a posix path:
27 > * - turns backward slashes into forward slashes
28 > * - makes it absolute if it starts with a drive letter
29 > * This should only be done for OS paths from Windows (or user provided paths potentially from Windows).
30 > * Using it on a Linux or MaxOS path might change it.
31 > */
32 > export function toPosixPath(osPath: string) {
33 > if (osPath.indexOf('/') === -1) { extpath.ts ×1
34 > osPath = toSlashes(osPath);
35 > }
36 > if (/^[a-zA-Z]:(\/|$)/.test(osPath)) { // starts with a drive letter
37 > osPath = '/' + osPath;
38 > }
39 > return osPath;
40 > }
42 > /**
43 > * Computes the _root_ this path, like `getRoot('c:\files') === c:\`,
44 > * `getRoot('files:///files/path') === files:///`,
45 > * or `getRoot('\\server\shares\path') === \\server\shares\`
46 > */
47 > export function getRoot(path: string, sep: string = posix.sep): string {
48 > if (!path) { extpath.ts ×4
49 return '';
50 }
52 > const len = path.length;
53 > const firstLetter = path.charCodeAt(0);
54 > if (isPathSeparator(firstLetter)) {
55 > if (isPathSeparator(path.charCodeAt(1))) {
56 > // UNC candidate \\localhost\shares\ddd extpath.ts ×2
57 > // ^^^^^^^^^^^^^^^^^^^
58 > if (!isPathSeparator(path.charCodeAt(2))) {
59 > let pos = 3;
60 > const start = pos;
61 > for (; pos < len; pos++) {
62 > if (isPathSeparator(path.charCodeAt(pos))) {
63 > break;
64 > }
65 > }
66 > if (start !== pos && !isPathSeparator(path.charCodeAt(pos + 1))) {
67 > pos += 1;
68 > for (; pos < len; pos++) {
69 > if (isPathSeparator(path.charCodeAt(pos))) {
70 > return path.slice(0, pos + 1) // consume this separator
71 > .replace(/[\\/]/g, sep);
72 > }
73 > }
74 > }
75 > }
76 > }
78 > // /user/far
79 > // ^
80 > return sep;
81 >
82 > } else if (isWindowsDriveLetter(firstLetter)) {
83 > // check for windows drive letter c:\ or c: extpath.ts ×2
84 >
85 > if (path.charCodeAt(1) === CharCode.Colon) {
86 > if (isPathSeparator(path.charCodeAt(2))) {
87 > // C:\fff
88 > // ^^^
89 > return path.slice(0, 2) + sep;
90 > } else {
91 > // C:
92 > // ^^
93 > return path.slice(0, 2);
94 > }
95 > }
96 > }
97 >
98 > // check for URI
99 > // scheme://authority/path
100 > // ^^^^^^^^^^^^^^^^^^^
101 > let pos = path.indexOf('://');
102 > if (pos !== -1) {
103 > pos += 3; // 3 -> "://".length
104 > for (; pos < len; pos++) {
105 > if (isPathSeparator(path.charCodeAt(pos))) {
106 > return path.slice(0, pos + 1); // consume this separator
107 > }
108 > }
109 > }
110 >
111 > return '';
112 > }
114 > /**
115 > * Check if the path follows this pattern: `\\hostname\sharename`.
116 > *
117 > * @see https://msdn.microsoft.com/en-us/library/gg465305.aspx
118 > * @return A boolean indication if the path is a UNC path, on none-windows
119 > * always false.
120 > */
121 > export function isUNC(path: string): boolean {
122 if (!isWindows) {
123 // UNC is a windows concept
124 return false;
125 }
126
127 if (!path || path.length < 5) {
128 // at least \\a\b
129 return false;
130 }
131
132 let code = path.charCodeAt(0);
133 if (code !== CharCode.Backslash) {
134 return false;
135 }
136
137 code = path.charCodeAt(1);
138
139 if (code !== CharCode.Backslash) {
140 return false;
141 }
142
143 let pos = 2;
144 const start = pos;
145 for (; pos < path.length; pos++) {
146 code = path.charCodeAt(pos);
147 if (code === CharCode.Backslash) {
148 break;
149 }
150 }
151
152 if (start === pos) {
153 return false;
154 }
155
156 code = path.charCodeAt(pos + 1);
157
158 if (isNaN(code) || code === CharCode.Backslash) {
159 return false;
160 }
161
162 return true;
163 }
165 > // Reference: https://en.wikipedia.org/wiki/Filename
166 > const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g;
167 > const UNIX_INVALID_FILE_CHARS = /[/]/g;
168 > const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])(\.(.*?))?$/i;
169 > export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean {
170 > const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS; extpath.ts ×6
171 >
172 > if (!name || name.length === 0 || /^\s+$/.test(name)) {
173 > return false; // require a name that is not just whitespace
174 > }
175 >
176 > invalidFileChars.lastIndex = 0; // the holy grail of software development
177 > if (invalidFileChars.test(name)) {
178 > return false; // check for certain invalid file characters
179 > }
180 >
181 > if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) {
182 return false; // check for certain invalid file names
183 }
185 > if (name === '.' || name === '..') {
186 return false; // check for reserved values
187 }
189 > if (isWindowsOS && name[name.length - 1] === '.') {
190 return false; // Windows: file cannot end with a "."
191 }
193 > if (isWindowsOS && name.length !== name.trim().length) {
194 return false; // Windows: file cannot end with a whitespace
195 }
197 > if (name.length > 255) {
198 return false; // most file systems do not allow files > 255 length
199 }
201 > return true;
202 > }
204 > /**
205 > * @deprecated please use `IUriIdentityService.extUri.isEqual` instead. If you are
206 > * in a context without services, consider to pass down the `extUri` from the outside
207 > * or use `extUriBiasedIgnorePathCase` if you know what you are doing.
208 > */
209 > export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boolean {
210 > const identityEquals = (pathA === pathB); extpath.ts ×1
211 > if (!ignoreCase || identityEquals) {
212 > return identityEquals;
213 > }
214 >
215 > if (!pathA || !pathB) {
216 > return false;
217 > }
218 >
219 > return equalsIgnoreCase(pathA, pathB);
220 > }
222 > /**
223 > * @deprecated please use `IUriIdentityService.extUri.isEqualOrParent` instead. If
224 > * you are in a context without services, consider to pass down the `extUri` from the
225 > * outside, or use `extUriBiasedIgnorePathCase` if you know what you are doing.
226 > */
227 > export function isEqualOrParent(base: string, parentCandidate: string, ignoreCase?: boolean, forcePosixSemantics = false): boolean {
228 > const separator = forcePosixSemantics ? posix.sep : sep; extpath.ts ×3
229 >
230 > if (base === parentCandidate) {
231 > return true; extpath.ts ×1
232 > }
234 > if (!base || !parentCandidate) { extpath.ts ×3
235 > return false; extpath.ts ×2
236 > }
238 > if (
239 > base.indexOf('..') >= 0 ||
240 > parentCandidate.indexOf('..') >= 0 extpath.ts ×1
241 > ) { extpath.ts ×3
242 > base = forcePosixSemantics ? posix.normalize(base) : normalize(base); extpath.ts ×1
243 > parentCandidate = forcePosixSemantics ? posix.normalize(parentCandidate) : normalize(parentCandidate);
244 > }
246 > if (parentCandidate.length > base.length) {
247 > return false; extpath.ts ×1
248 > }
250 > if (ignoreCase) {
251 > const beginsWith = startsWithIgnoreCase(base, parentCandidate); extpath.ts ×2
252 > if (!beginsWith) {
253 > return false; extpath.ts ×1
254 > }
256 > if (parentCandidate.length === base.length) {
257 > return true; // same path, different casing extpath.ts ×2
258 > }
260 > let sepOffset = parentCandidate.length;
261 > if (parentCandidate.charAt(parentCandidate.length - 1) === separator) {
262 > sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character extpath.ts ×1
263 > }
265 > return base.charAt(sepOffset) === separator;
266 > }
268 > if (parentCandidate.charAt(parentCandidate.length - 1) !== separator) {
269 > parentCandidate += separator; extpath.ts ×1
270 > }
272 > return base.indexOf(parentCandidate) === 0;
273 > }
275 > export function isWindowsDriveLetter(char0: number): boolean {
276 > return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z; extpath.ts ×1
277 > }
279 > export function sanitizeFilePath(candidate: string, cwd: string): string {
281 > // Special case: allow to open a drive letter without trailing backslash
282 > if (isWindows && candidate.endsWith(':')) {
283 candidate += sep;
284 }
286 > // Ensure absolute
287 > if (!isAbsolute(candidate)) {
288 > candidate = join(cwd, candidate);
289 > }
290 >
291 > // Ensure normalized
292 > candidate = normalize(candidate);
293 >
294 > // Ensure no trailing slash/backslash
295 > return removeTrailingPathSeparator(candidate);
296 > }
298 > export function removeTrailingPathSeparator(candidate: string): string {
299 > if (isWindows) { extpath.ts ×4
300 candidate = rtrim(candidate, sep);
301
302 // Special case: allow to open drive root ('C:\')
303 if (candidate.endsWith(':')) {
304 candidate += sep;
305 }
306
307 > } else { extpath.ts ×4
308 > candidate = rtrim(candidate, sep);
309 >
310 > // Special case: allow to open root ('/')
311 > if (!candidate) {
312 > candidate = sep;
313 > }
314 > }
315 >
316 > return candidate;
317 > }
319 > export function isRootOrDriveLetter(path: string): boolean {
320 > const pathNormalized = normalize(path); extpath.ts ×2
321 >
322 > if (isWindows) {
323 if (path.length > 3) {
324 return false;
325 }
326
327 return hasDriveLetter(pathNormalized) &&
328 (path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
329 }
331 > return pathNormalized === posix.sep;
332 > }
334 > export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean {
335 > if (isWindowsOS) { extpath.ts ×1
336 > return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === CharCode.Colon; extpath.ts ×1
337 > }
339 > return false;
340 > }
342 > export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined {
343 > return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined; extpath.ts ×1
344 > }
346 > export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number {
347 > if (candidate.length > path.length) { extpath.ts ×3
348 return -1;
349 }
351 > if (path === candidate) {
352 return 0;
353 }
355 > if (ignoreCase) {
356 > path = path.toLowerCase();
357 > candidate = candidate.toLowerCase();
358 > }
359 >
360 > return path.indexOf(candidate);
361 > }
363 > export interface IPathWithLineAndColumn {
364 > path: string;
365 > line?: number;
366 > column?: number;
367 > }
368 >
369 > export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
370 > const segments = rawPath.split(':'); // C:\file.txt:<line>:<column> extpath.ts ×2
371 >
372 > let path: string | undefined;
373 > let line: number | undefined;
374 > let column: number | undefined;
375 >
376 > for (const segment of segments) {
377 > const segmentAsNumber = Number(segment);
378 > if (!isNumber(segmentAsNumber)) {
379 > path = path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
380 > } else if (line === undefined) {
381 > line = segmentAsNumber;
382 > } else if (column === undefined) {
383 > column = segmentAsNumber;
384 > }
385 > }
386 >
387 > if (!path) {
388 throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
389 }
391 > return {
392 > path,
393 > line: line !== undefined ? line : undefined,
394 > column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
395 > };
396 > }
398 > const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
399 > const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789';
400 >
401 > export function randomPath(parent?: string, prefix?: string, randomLength = 8): string {
402 > let suffix = ''; extpath.ts ×3
403 > for (let i = 0; i < randomLength; i++) {
404 > let pathCharsTouse: string;
405 > if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) {
406
407 // Windows has certain reserved file names that cannot be used, such
408 // as AUX, CON, PRN, etc. We want to avoid generating a random name
409 // that matches that pattern, so we use a different set of characters
410 // for the first character of the name that does not include any of
411 // the reserved names first characters.
412
413 pathCharsTouse = windowsSafePathFirstChars;
414 > } else { extpath.ts ×3
415 > pathCharsTouse = pathChars;
416 > }
417 >
418 > suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length));
419 > }
420 >
421 > let randomFileName: string;
422 > if (prefix) {
423 > randomFileName = `${prefix}-${suffix}`; extpath.ts ×1
424 > } else { extpath.ts ×3
425 > randomFileName = suffix;
426 > }
427 >
428 > if (parent) {
429 > return join(parent, randomFileName); extpath.ts ×1
430 > }
432 > return randomFileName;
433 > }