forked from microsoft/rushstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFileError.ts
More file actions
182 lines (161 loc) · 6.34 KB
/
Copy pathFileError.ts
File metadata and controls
182 lines (161 loc) · 6.34 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
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { FileLocationStyle, Path } from './Path';
import { TypeUuid } from './TypeUuid';
/**
* Provides options for the creation of a FileError.
*
* @public
*/
export interface IFileErrorOptions {
/**
* The absolute path to the file that contains the error.
*/
absolutePath: string;
/**
* The root folder for the project that the error is in relation to.
*/
projectFolder: string;
/**
* The line number of the error in the target file. Minimum value is 1.
*/
line?: number;
/**
* The column number of the error in the target file. Minimum value is 1.
*/
column?: number;
}
/**
* Provides options for the output message of a file error.
*
* @public
*/
export interface IFileErrorFormattingOptions {
/**
* The format for the error message. If no format is provided, format 'Unix' is used by default.
*/
format?: FileLocationStyle;
}
const uuidFileError: string = '37a4c772-2dc8-4c66-89ae-262f8cc1f0c1';
const baseFolderEnvVar: string = 'RUSHSTACK_FILE_ERROR_BASE_FOLDER';
/**
* An `Error` subclass that should be thrown to report an unexpected state that specifically references
* a location in a file.
*
* @remarks The file path provided to the FileError constructor is expected to exist on disk. FileError
* should not be used for reporting errors that are not in reference to an existing file.
*
* @public
*/
export class FileError extends Error {
/** @internal */
public static _sanitizedEnvironmentVariable: string | undefined;
/** @internal */
public static _environmentVariableIsAbsolutePath: boolean = false;
private static _environmentVariableBasePathFnMap: ReadonlyMap<
string | undefined,
(fileError: FileError) => string | undefined
> = new Map([
[undefined, (fileError: FileError) => fileError.projectFolder],
['{PROJECT_FOLDER}', (fileError: FileError) => fileError.projectFolder],
['{ABSOLUTE_PATH}', (fileError: FileError) => undefined as string | undefined]
]);
/** {@inheritdoc IFileErrorOptions.absolutePath} */
public readonly absolutePath: string;
/** {@inheritdoc IFileErrorOptions.projectFolder} */
public readonly projectFolder: string;
/** {@inheritdoc IFileErrorOptions.line} */
public readonly line: number | undefined;
/** {@inheritdoc IFileErrorOptions.column} */
public readonly column: number | undefined;
/**
* Constructs a new instance of the {@link FileError} class.
*
* @param message - A message describing the error.
* @param options - Options for the error.
*/
public constructor(message: string, options: IFileErrorOptions) {
super(message);
this.absolutePath = options.absolutePath;
this.projectFolder = options.projectFolder;
this.line = options.line;
this.column = options.column;
// Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.
// https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
//
// Note: the prototype must also be set on any classes which extend this one
(this as any).__proto__ = FileError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any
}
/**
* Get the Unix-formatted the error message.
*
* @override
*/
public toString(): string {
// Default to formatting in 'Unix' format, for consistency.
return this.getFormattedErrorMessage();
}
/**
* Get the formatted error message.
*
* @param options - Options for the error message format.
*/
public getFormattedErrorMessage(options?: IFileErrorFormattingOptions): string {
return Path.formatFileLocation({
format: options?.format || 'Unix',
baseFolder: this._evaluateBaseFolder(),
pathToFormat: this.absolutePath,
message: this.message,
line: this.line,
column: this.column
});
}
private _evaluateBaseFolder(): string | undefined {
// Cache the sanitized environment variable. This means that we don't support changing
// the environment variable mid-execution. This is a reasonable tradeoff for the benefit
// of being able to cache absolute paths, since that is only able to be determined after
// running the regex, which is expensive. Since this would be a common execution path for
// tools like Rush, we should optimize for that.
if (!FileError._sanitizedEnvironmentVariable && process.env[baseFolderEnvVar]) {
// Strip leading and trailing quotes, if present.
FileError._sanitizedEnvironmentVariable = process.env[baseFolderEnvVar]!.replace(/^("|')|("|')$/g, '');
}
if (FileError._environmentVariableIsAbsolutePath) {
return FileError._sanitizedEnvironmentVariable;
}
// undefined environment variable has a mapping to the project folder
const baseFolderFn: ((fileError: FileError) => string | undefined) | undefined =
FileError._environmentVariableBasePathFnMap.get(FileError._sanitizedEnvironmentVariable);
if (baseFolderFn) {
return baseFolderFn(this);
}
const baseFolderTokenRegex: RegExp = /{([^}]+)}/g;
const result: RegExpExecArray | null = baseFolderTokenRegex.exec(
FileError._sanitizedEnvironmentVariable!
);
if (!result) {
// No tokens, assume absolute path
FileError._environmentVariableIsAbsolutePath = true;
return FileError._sanitizedEnvironmentVariable;
} else if (result.index !== 0) {
// Currently only support the token being first in the string.
throw new Error(
`The ${baseFolderEnvVar} environment variable contains text before the token "${result[0]}".`
);
} else if (result[0].length !== FileError._sanitizedEnvironmentVariable!.length) {
// Currently only support the token being the entire string.
throw new Error(
`The ${baseFolderEnvVar} environment variable contains text after the token "${result[0]}".`
);
} else {
throw new Error(
`The ${baseFolderEnvVar} environment variable contains a token "${result[0]}", which is not ` +
'supported.'
);
}
}
public static [Symbol.hasInstance](instance: object): boolean {
return TypeUuid.isInstanceOf(instance, uuidFileError);
}
}
TypeUuid.registerClass(FileError, uuidFileError);