123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- "use strict";
- // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
- // See LICENSE in the project root for license information.
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.generatePatchedLinterJsFileIfDoesNotExist = generatePatchedLinterJsFileIfDoesNotExist;
- const fs_1 = __importDefault(require("fs"));
- const constants_1 = require("./constants");
- /**
- * Dynamically generate file to properly patch many versions of ESLint
- * @param inputFilePath - Must be an iteration of https://github.com/eslint/eslint/blob/main/lib/linter/linter.js
- * @param outputFilePath - Some small changes to linter.js
- */
- function generatePatchedLinterJsFileIfDoesNotExist(inputFilePath, outputFilePath, eslintPackageVersion) {
- const generateEnvVarValue = process.env[constants_1.ESLINT_BULK_FORCE_REGENERATE_PATCH_ENV_VAR_NAME];
- if (generateEnvVarValue !== 'true' && generateEnvVarValue !== '1' && fs_1.default.existsSync(outputFilePath)) {
- return;
- }
- const majorVersion = parseInt(eslintPackageVersion, 10);
- const inputFile = fs_1.default.readFileSync(inputFilePath).toString();
- let inputIndex = 0;
- /**
- * Extract from the stream until marker is reached. When matching marker,
- * ignore whitespace in the stream and in the marker. Return the extracted text.
- */
- function scanUntilMarker(marker) {
- const trimmedMarker = marker.replace(/\s/g, '');
- let output = '';
- let trimmed = '';
- while (inputIndex < inputFile.length) {
- const char = inputFile[inputIndex++];
- output += char;
- if (!/^\s$/.test(char)) {
- trimmed += char;
- }
- if (trimmed.endsWith(trimmedMarker)) {
- return output;
- }
- }
- throw new Error('Unexpected end of input while looking for ' + JSON.stringify(marker));
- }
- function scanUntilNewline() {
- let output = '';
- while (inputIndex < inputFile.length) {
- const char = inputFile[inputIndex++];
- output += char;
- if (char === '\n') {
- return output;
- }
- }
- throw new Error('Unexpected end of input while looking for new line');
- }
- function scanUntilEnd() {
- const output = inputFile.substring(inputIndex);
- inputIndex = inputFile.length;
- return output;
- }
- const markerForStartOfClassMethodSpaces = '\n */\n ';
- const markerForStartOfClassMethodTabs = '\n\t */\n\t';
- function indexOfStartOfClassMethod(input, position) {
- let startOfClassMethodIndex = input.indexOf(markerForStartOfClassMethodSpaces, position);
- if (startOfClassMethodIndex === -1) {
- startOfClassMethodIndex = input.indexOf(markerForStartOfClassMethodTabs, position);
- if (startOfClassMethodIndex === -1) {
- return { index: startOfClassMethodIndex };
- }
- return { index: startOfClassMethodIndex, marker: markerForStartOfClassMethodTabs };
- }
- return { index: startOfClassMethodIndex, marker: markerForStartOfClassMethodSpaces };
- }
- /**
- * Returns index of next public method
- * @param fromIndex - index of inputFile to search if public method still exists
- * @returns -1 if public method does not exist or index of next public method
- */
- function getIndexOfNextMethod(fromIndex) {
- const rest = inputFile.substring(fromIndex);
- const endOfClassIndex = rest.indexOf('\n}');
- const { index: startOfClassMethodIndex, marker: startOfClassMethodMarker } = indexOfStartOfClassMethod(rest);
- if (startOfClassMethodIndex === -1 ||
- !startOfClassMethodMarker ||
- startOfClassMethodIndex > endOfClassIndex) {
- return { index: -1 };
- }
- const afterMarkerIndex = startOfClassMethodIndex + startOfClassMethodMarker.length;
- const isPublicMethod = rest[afterMarkerIndex] !== '_' &&
- rest[afterMarkerIndex] !== '#' &&
- !rest.substring(afterMarkerIndex, rest.indexOf('\n', afterMarkerIndex)).includes('static') &&
- !rest.substring(afterMarkerIndex, rest.indexOf('\n', afterMarkerIndex)).includes('constructor');
- return { index: fromIndex + afterMarkerIndex, isPublic: isPublicMethod };
- }
- function scanUntilIndex(indexToScanTo) {
- const output = inputFile.substring(inputIndex, indexToScanTo);
- inputIndex = indexToScanTo;
- return output;
- }
- let outputFile = '';
- // Match this:
- // //------------------------------------------------------------------------------
- // // Requirements
- // //------------------------------------------------------------------------------
- outputFile += scanUntilMarker('// Requirements');
- outputFile += scanUntilMarker('//--');
- outputFile += scanUntilNewline();
- outputFile += `
- // --- BEGIN MONKEY PATCH ---
- const bulkSuppressionsPatch = require(process.env.${constants_1.ESLINT_BULK_PATCH_PATH_ENV_VAR_NAME});
- const requireFromPathToLinterJS = bulkSuppressionsPatch.requireFromPathToLinterJS;
- `;
- // Match this:
- // //------------------------------------------------------------------------------
- // // Typedefs
- // //------------------------------------------------------------------------------
- const requireSection = scanUntilMarker('// Typedefs');
- // Match something like this:
- //
- // const path = require('path'),
- // eslintScope = require('eslint-scope'),
- // evk = require('eslint-visitor-keys'),
- //
- // Convert to something like this:
- //
- // const path = require('path'),
- // eslintScope = requireFromPathToLinterJS('eslint-scope'),
- // evk = requireFromPathToLinterJS('eslint-visitor-keys'),
- //
- outputFile += requireSection.replace(/require\s*\((?:'([^']+)'|"([^"]+)")\)/g, (match, p1, p2) => {
- var _a;
- const importPath = (_a = p1 !== null && p1 !== void 0 ? p1 : p2) !== null && _a !== void 0 ? _a : '';
- if (importPath !== 'path') {
- if (p1) {
- return `requireFromPathToLinterJS('${p1}')`;
- }
- if (p2) {
- return `requireFromPathToLinterJS("${p2}")`;
- }
- }
- // Keep as-is
- return match;
- });
- outputFile += `--- END MONKEY PATCH ---
- `;
- if (majorVersion >= 9) {
- outputFile += scanUntilMarker('const emitter = createEmitter();');
- outputFile += `
- // --- BEGIN MONKEY PATCH ---
- let currentNode = undefined;
- // --- END MONKEY PATCH ---`;
- }
- // Match this:
- // ```
- // if (reportTranslator === null) {
- // reportTranslator = createReportTranslator({
- // ruleId,
- // severity,
- // sourceCode,
- // messageIds,
- // disableFixes
- // });
- // }
- // const problem = reportTranslator(...args);
- //
- // if (problem.fix && !(rule.meta && rule.meta.fixable)) {
- // throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
- // }
- // ```
- //
- // Convert to something like this:
- // ```
- // if (reportTranslator === null) {
- // reportTranslator = createReportTranslator({
- // ruleId,
- // severity,
- // sourceCode,
- // messageIds,
- // disableFixes
- // });
- // }
- // const problem = reportTranslator(...args);
- // // --- BEGIN MONKEY PATCH ---
- // if (bulkSuppressionsPatch.shouldBulkSuppress({ filename, currentNode: args[0]?.node ?? currentNode, ruleId, problem })) return;
- // // --- END MONKEY PATCH ---
- //
- // if (problem.fix && !(rule.meta && rule.meta.fixable)) {
- // throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\".");
- // }
- // ```
- outputFile += scanUntilMarker('const problem = reportTranslator(...args);');
- outputFile += `
- // --- BEGIN MONKEY PATCH ---
- if (bulkSuppressionsPatch.shouldBulkSuppress({ filename, currentNode: args[0]?.node ?? currentNode, ruleId, problem })) return;
- // --- END MONKEY PATCH ---`;
- //
- // Match this:
- // ```
- // Object.keys(ruleListeners).forEach(selector => {
- // ...
- // });
- // ```
- //
- // Convert to something like this:
- // ```
- // Object.keys(ruleListeners).forEach(selector => {
- // // --- BEGIN MONKEY PATCH ---
- // emitter.on(selector, (...args) => { currentNode = args[args.length - 1]; });
- // // --- END MONKEY PATCH ---
- // ...
- // });
- // ```
- if (majorVersion >= 9) {
- outputFile += scanUntilMarker('Object.keys(ruleListeners).forEach(selector => {');
- outputFile += `
- // --- BEGIN MONKEY PATCH ---
- emitter.on(selector, (...args) => { currentNode = args[args.length - 1]; });
- // --- END MONKEY PATCH ---`;
- }
- outputFile += scanUntilMarker('class Linter {');
- outputFile += scanUntilNewline();
- outputFile += `
- // --- BEGIN MONKEY PATCH ---
- /**
- * We intercept ESLint execution at the .eslintrc.js file, but unfortunately the Linter class is
- * initialized before the .eslintrc.js file is executed. This means the internalSlotsMap that all
- * the patched methods refer to is not initialized. This method checks if the internalSlotsMap is
- * initialized, and if not, initializes it.
- */
- _conditionallyReinitialize({ cwd, configType } = {}) {
- if (internalSlotsMap.get(this) === undefined) {
- internalSlotsMap.set(this, {
- cwd: normalizeCwd(cwd),
- lastConfigArray: null,
- lastSourceCode: null,
- lastSuppressedMessages: [],
- configType, // TODO: Remove after flat config conversion
- parserMap: new Map([['espree', espree]]),
- ruleMap: new Rules()
- });
- this.version = pkg.version;
- }
- }
- // --- END MONKEY PATCH ---
- `;
- const privateMethodNames = [];
- let { index: indexOfNextMethod, isPublic } = getIndexOfNextMethod(inputIndex);
- while (indexOfNextMethod !== -1) {
- outputFile += scanUntilIndex(indexOfNextMethod);
- if (isPublic) {
- // Inject the monkey patch at the start of the public method
- outputFile += scanUntilNewline();
- outputFile += ` // --- BEGIN MONKEY PATCH ---
- this._conditionallyReinitialize();
- // --- END MONKEY PATCH ---
- `;
- }
- else if (inputFile[inputIndex] === '#') {
- // Replace the '#' private method with a '_' private method, so that our monkey patch
- // can still call it. Otherwise, we get the following error during execution:
- // TypeError: Receiver must be an instance of class Linter
- const privateMethodName = scanUntilMarker('(');
- // Remove the '(' at the end and stash it, since we need to escape it for the regex later
- privateMethodNames.push(privateMethodName.slice(0, -1));
- outputFile += `_${privateMethodName.slice(1)}`;
- }
- const indexResult = getIndexOfNextMethod(inputIndex);
- indexOfNextMethod = indexResult.index;
- isPublic = indexResult.isPublic;
- }
- outputFile += scanUntilEnd();
- // Do a second pass to find and replace all calls to private methods with the patched versions.
- if (privateMethodNames.length) {
- // eslint-disable-next-line @rushstack/security/no-unsafe-regexp
- const privateMethodCallRegex = new RegExp(`\.(${privateMethodNames.join('|')})\\(`, 'g');
- outputFile = outputFile.replace(privateMethodCallRegex, (match, privateMethodName) => {
- // Replace the leading '#' with a leading '_'
- return `._${privateMethodName.slice(1)}(`;
- });
- }
- fs_1.default.writeFileSync(outputFilePath, outputFile);
- }
- //# sourceMappingURL=generate-patched-file.js.map
|