source: frontend/node_modules/@rushstack/eslint-patch/lib-esm/eslint-bulk-suppressions/generate-patched-file.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance

  • Property mode set to 100644
File size: 14.3 KB
Line 
1// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2// See LICENSE in the project root for license information.
3import fs from 'node:fs';
4import { ESLINT_BULK_FORCE_REGENERATE_PATCH_ENV_VAR_NAME, ESLINT_BULK_PATCH_PATH_ENV_VAR_NAME } from './constants';
5/**
6 * Dynamically generate file to properly patch many versions of ESLint
7 * @param inputFilePath - Must be an iteration of https://github.com/eslint/eslint/blob/main/lib/linter/linter.js
8 * @param outputFilePath - Some small changes to linter.js
9 */
10export function generatePatchedLinterJsFileIfDoesNotExist(inputFilePath, outputFilePath, eslintPackageVersion) {
11 const generateEnvVarValue = process.env[ESLINT_BULK_FORCE_REGENERATE_PATCH_ENV_VAR_NAME];
12 if (generateEnvVarValue !== 'true' && generateEnvVarValue !== '1' && fs.existsSync(outputFilePath)) {
13 return;
14 }
15 const [majorVersionString, minorVersionString] = eslintPackageVersion.split('.');
16 const majorVersion = parseInt(majorVersionString, 10);
17 const minorVersion = parseInt(minorVersionString, 10);
18 const inputFile = fs.readFileSync(inputFilePath).toString();
19 let inputIndex = 0;
20 /**
21 * Extract from the stream until marker is reached. When matching marker,
22 * ignore whitespace in the stream and in the marker. Return the extracted text.
23 */
24 function scanUntilMarker(marker) {
25 const trimmedMarker = marker.replace(/\s/g, '');
26 let output = '';
27 let trimmed = '';
28 while (inputIndex < inputFile.length) {
29 const char = inputFile[inputIndex++];
30 output += char;
31 if (!/^\s$/.test(char)) {
32 trimmed += char;
33 }
34 if (trimmed.endsWith(trimmedMarker)) {
35 return output;
36 }
37 }
38 throw new Error('Unexpected end of input while looking for ' + JSON.stringify(marker));
39 }
40 function scanUntilNewline() {
41 let output = '';
42 while (inputIndex < inputFile.length) {
43 const char = inputFile[inputIndex++];
44 output += char;
45 if (char === '\n') {
46 return output;
47 }
48 }
49 throw new Error('Unexpected end of input while looking for new line');
50 }
51 function scanUntilEnd() {
52 const output = inputFile.substring(inputIndex);
53 inputIndex = inputFile.length;
54 return output;
55 }
56 const markerForStartOfClassMethodSpaces = '\n */\n ';
57 const markerForStartOfClassMethodTabs = '\n\t */\n\t';
58 function indexOfStartOfClassMethod(input, position) {
59 let startOfClassMethodIndex = input.indexOf(markerForStartOfClassMethodSpaces, position);
60 if (startOfClassMethodIndex === -1) {
61 startOfClassMethodIndex = input.indexOf(markerForStartOfClassMethodTabs, position);
62 if (startOfClassMethodIndex === -1) {
63 return { index: startOfClassMethodIndex };
64 }
65 return { index: startOfClassMethodIndex, marker: markerForStartOfClassMethodTabs };
66 }
67 return { index: startOfClassMethodIndex, marker: markerForStartOfClassMethodSpaces };
68 }
69 /**
70 * Returns index of next public method
71 * @param fromIndex - index of inputFile to search if public method still exists
72 * @returns -1 if public method does not exist or index of next public method
73 */
74 function getIndexOfNextMethod(fromIndex) {
75 const rest = inputFile.substring(fromIndex);
76 const endOfClassIndex = rest.indexOf('\n}');
77 const { index: startOfClassMethodIndex, marker: startOfClassMethodMarker } = indexOfStartOfClassMethod(rest);
78 if (startOfClassMethodIndex === -1 ||
79 !startOfClassMethodMarker ||
80 startOfClassMethodIndex > endOfClassIndex) {
81 return { index: -1 };
82 }
83 const afterMarkerIndex = startOfClassMethodIndex + startOfClassMethodMarker.length;
84 const isPublicMethod = rest[afterMarkerIndex] !== '_' &&
85 rest[afterMarkerIndex] !== '#' &&
86 !rest.substring(afterMarkerIndex, rest.indexOf('\n', afterMarkerIndex)).includes('static') &&
87 !rest.substring(afterMarkerIndex, rest.indexOf('\n', afterMarkerIndex)).includes('constructor');
88 return { index: fromIndex + afterMarkerIndex, isPublic: isPublicMethod };
89 }
90 function scanUntilIndex(indexToScanTo) {
91 const output = inputFile.substring(inputIndex, indexToScanTo);
92 inputIndex = indexToScanTo;
93 return output;
94 }
95 let outputFile = '';
96 // Match this:
97 // //------------------------------------------------------------------------------
98 // // Requirements
99 // //------------------------------------------------------------------------------
100 outputFile += scanUntilMarker('// Requirements');
101 outputFile += scanUntilMarker('//--');
102 outputFile += scanUntilNewline();
103 outputFile += `
104// --- BEGIN MONKEY PATCH ---
105const bulkSuppressionsPatch = require(process.env.${ESLINT_BULK_PATCH_PATH_ENV_VAR_NAME});
106const requireFromPathToLinterJS = bulkSuppressionsPatch.requireFromPathToLinterJS;
107`;
108 // Match this:
109 // //------------------------------------------------------------------------------
110 // // Typedefs
111 // //------------------------------------------------------------------------------
112 const requireSection = scanUntilMarker('// Typedefs');
113 // Match something like this:
114 //
115 // const path = require('path'),
116 // eslintScope = require('eslint-scope'),
117 // evk = require('eslint-visitor-keys'),
118 //
119 // Convert to something like this:
120 //
121 // const path = require('path'),
122 // eslintScope = requireFromPathToLinterJS('eslint-scope'),
123 // evk = requireFromPathToLinterJS('eslint-visitor-keys'),
124 //
125 outputFile += requireSection.replace(/require\s*\((?:'([^']+)'|"([^"]+)")\)/g, (match, p1, p2) => {
126 var _a;
127 const importPath = (_a = p1 !== null && p1 !== void 0 ? p1 : p2) !== null && _a !== void 0 ? _a : '';
128 if (importPath !== 'path') {
129 if (p1) {
130 return `requireFromPathToLinterJS('${p1}')`;
131 }
132 if (p2) {
133 return `requireFromPathToLinterJS("${p2}")`;
134 }
135 }
136 // Keep as-is
137 return match;
138 });
139 outputFile += `--- END MONKEY PATCH ---
140`;
141 if (majorVersion >= 9) {
142 if (minorVersion >= 37) {
143 outputFile += scanUntilMarker('const visitor = new SourceCodeVisitor();');
144 }
145 else {
146 outputFile += scanUntilMarker('const emitter = createEmitter();');
147 }
148 outputFile += `
149 // --- BEGIN MONKEY PATCH ---
150 let currentNode = undefined;
151 // --- END MONKEY PATCH ---`;
152 }
153 // Match this (9.25.1):
154 // ```
155 // if (reportTranslator === null) {
156 // reportTranslator = createReportTranslator({
157 // ruleId,
158 // severity,
159 // sourceCode,
160 // messageIds,
161 // disableFixes
162 // });
163 // }
164 // const problem = reportTranslator(...args);
165 //
166 // if (problem.fix && !(rule.meta && rule.meta.fixable)) {
167 // throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
168 // }
169 // ```
170 // Or this (9.37.0):
171 // ```
172 // const problem = report.addRuleMessage(
173 // ruleId,
174 // severity,
175 // ...args,
176 // );
177 //
178 // if (problem.fix && !(rule.meta && rule.meta.fixable)) {
179 // throw new Error(
180 // 'Fixable rules must set the `meta.fixable` property to "code" or "whitespace".',
181 // );
182 // }
183 // ```
184 //
185 // Convert to something like this (9.25.1):
186 // ```
187 // if (reportTranslator === null) {
188 // reportTranslator = createReportTranslator({
189 // ruleId,
190 // severity,
191 // sourceCode,
192 // messageIds,
193 // disableFixes
194 // });
195 // }
196 // const problem = reportTranslator(...args);
197 // // --- BEGIN MONKEY PATCH ---
198 // if (bulkSuppressionsPatch.shouldBulkSuppress({ filename, currentNode: args[0]?.node ?? currentNode, ruleId, problem })) return;
199 // // --- END MONKEY PATCH ---
200 //
201 // if (problem.fix && !(rule.meta && rule.meta.fixable)) {
202 // throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
203 // }
204 // ```
205 // Or this (9.37.0):
206 // ```
207 // const problem = report.addRuleMessage(
208 // ruleId,
209 // severity,
210 // ...args,
211 // );
212 // // --- BEGIN MONKEY PATCH ---
213 // if (bulkSuppressionsPatch.shouldBulkSuppress({ filename, currentNode: args[0]?.node ?? currentNode, ruleId, problem })) return;
214 // // --- END MONKEY PATCH ---
215 //
216 // if (problem.fix && !(rule.meta && rule.meta.fixable)) {
217 // throw new Error(
218 // 'Fixable rules must set the `meta.fixable` property to "code" or "whitespace".',
219 // );
220 // }
221 // ```
222 if (majorVersion > 9 || (majorVersion === 9 && minorVersion >= 37)) {
223 outputFile += scanUntilMarker('const problem = report.addRuleMessage(');
224 outputFile += scanUntilMarker('ruleId,');
225 outputFile += scanUntilMarker('severity,');
226 outputFile += scanUntilMarker('...args,');
227 outputFile += scanUntilMarker(');');
228 }
229 else {
230 outputFile += scanUntilMarker('const problem = reportTranslator(...args);');
231 }
232 outputFile += `
233 // --- BEGIN MONKEY PATCH ---`;
234 if (majorVersion > 9 || (majorVersion === 9 && minorVersion >= 37)) {
235 outputFile += `
236 if (bulkSuppressionsPatch.shouldBulkSuppress({ filename, currentNode: args[0]?.node ?? currentNode, ruleId, problem })) {
237 problem.suppressions ??= []; problem.suppressions.push({kind:"bulk",justification:""});
238 }`;
239 }
240 else {
241 outputFile += `
242 if (bulkSuppressionsPatch.shouldBulkSuppress({ filename, currentNode: args[0]?.node ?? currentNode, ruleId, problem })) return;`;
243 }
244 outputFile += `
245 // --- END MONKEY PATCH ---`;
246 //
247 // Match this:
248 // ```
249 // Object.keys(ruleListeners).forEach(selector => {
250 // ...
251 // });
252 // ```
253 //
254 // Convert to something like this (9.25.1):
255 // ```
256 // Object.keys(ruleListeners).forEach(selector => {
257 // // --- BEGIN MONKEY PATCH ---
258 // emitter.on(selector, (...args) => { currentNode = args[args.length - 1]; });
259 // // --- END MONKEY PATCH ---
260 // ...
261 // });
262 // ```
263 // Or this (9.37.0):
264 // ```
265 // Object.keys(ruleListeners).forEach(selector => {
266 // // --- BEGIN MONKEY PATCH ---
267 // visitor.add(selector, (...args) => { currentNode = args[args.length - 1]; });
268 // // --- END MONKEY PATCH ---
269 // ...
270 // });
271 // ```
272 if (majorVersion >= 9) {
273 outputFile += scanUntilMarker('Object.keys(ruleListeners).forEach(selector => {');
274 outputFile += `
275 // --- BEGIN MONKEY PATCH ---
276`;
277 if (minorVersion >= 37) {
278 outputFile += `visitor.add(selector, (...args) => { currentNode = args[args.length - 1]; });`;
279 }
280 else {
281 outputFile += `emitter.on(selector, (...args) => { currentNode = args[args.length - 1]; });`;
282 }
283 outputFile += `
284 // --- END MONKEY PATCH ---`;
285 }
286 outputFile += scanUntilMarker('class Linter {');
287 outputFile += scanUntilNewline();
288 outputFile += `
289 // --- BEGIN MONKEY PATCH ---
290 /**
291 * We intercept ESLint execution at the .eslintrc.js file, but unfortunately the Linter class is
292 * initialized before the .eslintrc.js file is executed. This means the internalSlotsMap that all
293 * the patched methods refer to is not initialized. This method checks if the internalSlotsMap is
294 * initialized, and if not, initializes it.
295 */
296 _conditionallyReinitialize({ cwd, configType } = {}) {
297 if (internalSlotsMap.get(this) === undefined) {
298 internalSlotsMap.set(this, {
299 cwd: normalizeCwd(cwd),
300 flags: [],
301 lastConfigArray: null,
302 lastSourceCode: null,
303 lastSuppressedMessages: [],
304 configType, // TODO: Remove after flat config conversion
305 parserMap: new Map([['espree', espree]]),
306 ruleMap: new Rules()
307 });
308
309 this.version = pkg.version;
310 }
311 }
312 // --- END MONKEY PATCH ---
313`;
314 const privateMethodNames = [];
315 let { index: indexOfNextMethod, isPublic } = getIndexOfNextMethod(inputIndex);
316 while (indexOfNextMethod !== -1) {
317 outputFile += scanUntilIndex(indexOfNextMethod);
318 if (isPublic) {
319 // Inject the monkey patch at the start of the public method
320 outputFile += scanUntilNewline();
321 outputFile += ` // --- BEGIN MONKEY PATCH ---
322 this._conditionallyReinitialize();
323 // --- END MONKEY PATCH ---
324`;
325 }
326 else if (inputFile[inputIndex] === '#') {
327 // Replace the '#' private method with a '_' private method, so that our monkey patch
328 // can still call it. Otherwise, we get the following error during execution:
329 // TypeError: Receiver must be an instance of class Linter
330 const privateMethodName = scanUntilMarker('(');
331 // Remove the '(' at the end and stash it, since we need to escape it for the regex later
332 privateMethodNames.push(privateMethodName.slice(0, -1));
333 outputFile += `_${privateMethodName.slice(1)}`;
334 }
335 const indexResult = getIndexOfNextMethod(inputIndex);
336 indexOfNextMethod = indexResult.index;
337 isPublic = indexResult.isPublic;
338 }
339 outputFile += scanUntilEnd();
340 // Do a second pass to find and replace all calls to private methods with the patched versions.
341 if (privateMethodNames.length) {
342 const privateMethodCallRegex = new RegExp(`\.(${privateMethodNames.join('|')})\\(`, 'g');
343 outputFile = outputFile.replace(privateMethodCallRegex, (match, privateMethodName) => {
344 // Replace the leading '#' with a leading '_'
345 return `._${privateMethodName.slice(1)}(`;
346 });
347 }
348 fs.writeFileSync(outputFilePath, outputFile);
349}
350//# sourceMappingURL=generate-patched-file.js.map
Note: See TracBrowser for help on using the repository browser.