forked from stainless-api/rushstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPath.ts
More file actions
267 lines (242 loc) · 8.76 KB
/
Path.ts
File metadata and controls
267 lines (242 loc) · 8.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as path from 'node:path';
/**
* The format that the FileError message should conform to. The supported formats are:
* - Unix: `<path>:<line>:<column> - <message>`
* - VisualStudio: `<path>(<line>,<column>) - <message>`
*
* @public
*/
export type FileLocationStyle = 'Unix' | 'VisualStudio';
/**
* Options for {@link Path.formatFileLocation}.
* @public
*/
export interface IPathFormatFileLocationOptions {
/**
* The base path to use when converting `pathToFormat` to a relative path. If not specified,
* `pathToFormat` will be used as-is.
*/
baseFolder?: string;
/**
* The path that will be used to specify the file location.
*/
pathToFormat: string;
/**
* The message related to the file location.
*/
message: string;
/**
* The style of file location formatting to use.
*/
format: FileLocationStyle;
/**
* The optional line number. If not specified, the line number will not be included
* in the formatted string.
*/
line?: number;
/**
* The optional column number. If not specified, the column number will not be included
* in the formatted string.
*/
column?: number;
}
/**
* Options for {@link Path.formatConcisely}.
* @public
*/
export interface IPathFormatConciselyOptions {
/**
* The path to be converted.
*/
pathToConvert: string;
/**
* The base path to use when converting `pathToConvert` to a relative path.
*/
baseFolder: string;
/**
* If set to true, don't include the leading `./` if the path is under the base folder.
*/
trimLeadingDotSlash?: boolean;
}
/**
* Common operations for manipulating file and directory paths.
* @remarks
* This API is intended to eventually be a complete replacement for the NodeJS "path" API.
* @public
*/
export class Path {
// Matches a relative path consisting entirely of periods and slashes
// Example: ".", "..", "../..", etc
private static _relativePathRegex: RegExp = /^[.\/\\]+$/;
// Matches a relative path segment that traverses upwards
// Example: "a/../b"
private static _upwardPathSegmentRegex: RegExp = /([\/\\]|^)\.\.([\/\\]|$)/;
/**
* Returns true if "childPath" is located inside the "parentFolderPath" folder
* or one of its child folders. Note that "parentFolderPath" is not considered to be
* under itself. The "childPath" can refer to any type of file system object.
*
* @remarks
* The indicated file/folder objects are not required to actually exist on disk.
* For example, "parentFolderPath" is interpreted as a folder name even if it refers to a file.
* If the paths are relative, they will first be resolved using path.resolve().
*/
public static isUnder(childPath: string, parentFolderPath: string): boolean {
// If childPath is under parentPath, then relativePath will be something like
// "../.." or "..\\..", which consists entirely of periods and slashes.
// (Note that something like "....t" is actually a valid filename, but "...." is not.)
const relativePath: string = path.relative(childPath, parentFolderPath);
return Path._relativePathRegex.test(relativePath);
}
/**
* Returns true if "childPath" is equal to "parentFolderPath", or if it is inside that folder
* or one of its children. The "childPath" can refer to any type of file system object.
*
* @remarks
* The indicated file/folder objects are not required to actually exist on disk.
* For example, "parentFolderPath" is interpreted as a folder name even if it refers to a file.
* If the paths are relative, they will first be resolved using path.resolve().
*/
public static isUnderOrEqual(childPath: string, parentFolderPath: string): boolean {
const relativePath: string = path.relative(childPath, parentFolderPath);
return relativePath === '' || Path._relativePathRegex.test(relativePath);
}
/**
* Returns true if `path1` and `path2` refer to the same underlying path.
*
* @remarks
*
* The comparison is performed using `path.relative()`.
*/
public static isEqual(path1: string, path2: string): boolean {
return path.relative(path1, path2) === '';
}
/**
* Formats a path to look nice for reporting purposes.
* @remarks
* If `pathToConvert` is under the `baseFolder`, then it will be converted to a relative with the `./` prefix
* unless the {@link IPathFormatConciselyOptions.trimLeadingDotSlash} option is set to `true`.
* Otherwise, it will be converted to an absolute path.
*
* Backslashes will be converted to slashes, unless the path starts with an OS-specific string like `C:\`.
*/
public static formatConcisely(options: IPathFormatConciselyOptions): string {
// Same logic as Path.isUnderOrEqual()
const relativePath: string = path.relative(options.pathToConvert, options.baseFolder);
const isUnderOrEqual: boolean = relativePath === '' || Path._relativePathRegex.test(relativePath);
if (isUnderOrEqual) {
// Note that isUnderOrEqual()'s relativePath is the reverse direction
const convertedPath: string = Path.convertToSlashes(
path.relative(options.baseFolder, options.pathToConvert)
);
if (options.trimLeadingDotSlash) {
return convertedPath;
} else {
return `./${convertedPath}`;
}
}
const absolutePath: string = path.resolve(options.pathToConvert);
return absolutePath;
}
/**
* Formats a file location to look nice for reporting purposes.
* @remarks
* If `pathToFormat` is under the `baseFolder`, then it will be converted to a relative with the `./` prefix.
* Otherwise, it will be converted to an absolute path.
*
* Backslashes will be converted to slashes, unless the path starts with an OS-specific string like `C:\`.
*/
public static formatFileLocation(options: IPathFormatFileLocationOptions): string {
const { message, format, pathToFormat, baseFolder, line, column } = options;
// Convert the path to be relative to the base folder, if specified. Otherwise, use
// the path as-is.
const filePath: string = baseFolder
? Path.formatConcisely({
pathToConvert: pathToFormat,
baseFolder,
trimLeadingDotSlash: true
})
: path.resolve(pathToFormat);
let formattedFileLocation: string;
switch (format) {
case 'Unix': {
if (line !== undefined && column !== undefined) {
formattedFileLocation = `:${line}:${column}`;
} else if (line !== undefined) {
formattedFileLocation = `:${line}`;
} else {
formattedFileLocation = '';
}
break;
}
case 'VisualStudio': {
if (line !== undefined && column !== undefined) {
formattedFileLocation = `(${line},${column})`;
} else if (line !== undefined) {
formattedFileLocation = `(${line})`;
} else {
formattedFileLocation = '';
}
break;
}
default: {
throw new Error(`Unknown format: ${format}`);
}
}
return `${filePath}${formattedFileLocation} - ${message}`;
}
/**
* Replaces Windows-style backslashes with POSIX-style slashes.
*
* @remarks
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
public static convertToSlashes(inputPath: string): string {
return inputPath.replace(/\\/g, '/');
}
/**
* Replaces POSIX-style slashes with Windows-style backslashes
*
* @remarks
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
public static convertToBackslashes(inputPath: string): string {
return inputPath.replace(/\//g, '\\');
}
/**
* Replaces slashes or backslashes with the appropriate slash for the current operating system.
*/
public static convertToPlatformDefault(inputPath: string): string {
return path.sep === '/' ? Path.convertToSlashes(inputPath) : Path.convertToBackslashes(inputPath);
}
/**
* Returns true if the specified path is a relative path and does not use `..` to walk upwards.
*
* @example
* ```ts
* // These evaluate to true
* isDownwardRelative('folder');
* isDownwardRelative('file');
* isDownwardRelative('folder/');
* isDownwardRelative('./folder/');
* isDownwardRelative('./folder/file');
*
* // These evaluate to false
* isDownwardRelative('../folder');
* isDownwardRelative('folder/../file');
* isDownwardRelative('/folder/file');
* ```
*/
public static isDownwardRelative(inputPath: string): boolean {
if (path.isAbsolute(inputPath)) {
return false;
}
// Does it contain ".."
if (Path._upwardPathSegmentRegex.test(inputPath)) {
return false;
}
return true;
}
}