273 lines
12 KiB
JavaScript
273 lines
12 KiB
JavaScript
|
"use strict";
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||
|
// See LICENSE in the project root for license information.
|
||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
|
}
|
||
|
Object.defineProperty(o, k2, desc);
|
||
|
}) : (function(o, m, k, k2) {
|
||
|
if (k2 === undefined) k2 = k;
|
||
|
o[k2] = m[k];
|
||
|
}));
|
||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
|
}) : function(o, v) {
|
||
|
o["default"] = v;
|
||
|
});
|
||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||
|
if (mod && mod.__esModule) return mod;
|
||
|
var result = {};
|
||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
|
__setModuleDefault(result, mod);
|
||
|
return result;
|
||
|
};
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.patchClass = exports.requireFromPathToLinterJS = exports.BulkSuppressionsPrune = exports.onFinish = exports.shouldBulkSuppress = exports.GitRootPath = exports.getGitRootPath = void 0;
|
||
|
const child_process_1 = require("child_process");
|
||
|
const fs_1 = __importDefault(require("fs"));
|
||
|
const path_1 = __importDefault(require("path"));
|
||
|
const Guards = __importStar(require("./ast-guards"));
|
||
|
const _patch_base_1 = require("../_patch-base");
|
||
|
function getNodeName(node) {
|
||
|
if (!Guards.isNodeWithName(node))
|
||
|
return null;
|
||
|
if (Guards.isClassDeclarationWithName(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isFunctionDeclarationWithName(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isClassExpressionWithName(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isFunctionExpressionWithName(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isNormalVariableDeclaratorWithAnonymousExpressionAssigned(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isNormalObjectPropertyWithAnonymousExpressionAssigned(node))
|
||
|
return node.key.name;
|
||
|
if (Guards.isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned(node))
|
||
|
return node.key.name;
|
||
|
if (Guards.isNormalAssignmentPatternWithAnonymousExpressionAssigned(node))
|
||
|
return node.left.name;
|
||
|
if (Guards.isNormalMethodDefinition(node))
|
||
|
return node.key.name;
|
||
|
if (Guards.isTSEnumDeclaration(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isTSInterfaceDeclaration(node))
|
||
|
return node.id.name;
|
||
|
if (Guards.isTSTypeAliasDeclaration(node))
|
||
|
return node.id.name;
|
||
|
return null;
|
||
|
}
|
||
|
function calculateScopeId(node) {
|
||
|
const scopeIds = [];
|
||
|
for (let current = node; current; current = current.parent) {
|
||
|
const scopeIdForASTNode = getNodeName(current);
|
||
|
if (scopeIdForASTNode !== null)
|
||
|
scopeIds.unshift(scopeIdForASTNode);
|
||
|
}
|
||
|
if (scopeIds.length === 0)
|
||
|
return '.';
|
||
|
return '.' + scopeIds.join('.');
|
||
|
}
|
||
|
/**
|
||
|
* @throws Throws an error if the command to retrieve the root path fails.
|
||
|
* @returns The root path of the monorepo.
|
||
|
*/
|
||
|
function getGitRootPath() {
|
||
|
const result = (0, child_process_1.spawnSync)('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' });
|
||
|
if (result.status !== 0)
|
||
|
throw new Error(`get root path failed`);
|
||
|
return result.stdout.toString().trim();
|
||
|
}
|
||
|
exports.getGitRootPath = getGitRootPath;
|
||
|
exports.GitRootPath = getGitRootPath();
|
||
|
function findEslintrcDirectory(fileAbsolutePath) {
|
||
|
for (let currentDir = fileAbsolutePath; currentDir.startsWith(exports.GitRootPath); currentDir = path_1.default.dirname(currentDir))
|
||
|
if (['.eslintrc.js', '.eslintrc.cjs'].some((eslintrc) => fs_1.default.existsSync(path_1.default.join(currentDir, eslintrc))))
|
||
|
return currentDir;
|
||
|
throw new Error('Cannot locate eslintrc');
|
||
|
}
|
||
|
function validateSuppressionsJson(json) {
|
||
|
if (typeof json !== 'object')
|
||
|
throw new Error(`Invalid JSON object: ${JSON.stringify(json, null, 2)}`);
|
||
|
if (json === null)
|
||
|
throw new Error('JSON object is null.');
|
||
|
if (!json.hasOwnProperty('suppressions'))
|
||
|
throw new Error('Missing "suppressions" property.');
|
||
|
if (!Array.isArray(json.suppressions))
|
||
|
throw new Error('"suppressions" property is not an array.');
|
||
|
if (!json.suppressions.every((suppression) => {
|
||
|
if (typeof suppression !== 'object')
|
||
|
throw new Error(`Invalid suppression: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (suppression === null)
|
||
|
throw new Error(`Suppression is null: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (!suppression.hasOwnProperty('file'))
|
||
|
throw new Error(`Missing "file" property in suppression: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (typeof suppression.file !== 'string')
|
||
|
throw new Error(`"file" property in suppression is not a string: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (!suppression.hasOwnProperty('scopeId'))
|
||
|
throw new Error(`Missing "scopeId" property in suppression: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (typeof suppression.scopeId !== 'string')
|
||
|
throw new Error(`"scopeId" property in suppression is not a string: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (!suppression.hasOwnProperty('rule'))
|
||
|
throw new Error(`Missing "rule" property in suppression: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
if (typeof suppression.rule !== 'string')
|
||
|
throw new Error(`"rule" property in suppression is not a string: ${JSON.stringify(suppression, null, 2)}`);
|
||
|
return true;
|
||
|
})) {
|
||
|
throw new Error(`Invalid suppression in "suppressions" array: ${JSON.stringify(json.suppressions, null, 2)}`);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
function readSuppressionsJson(fileAbsolutePath) {
|
||
|
const eslintrcDirectory = findEslintrcDirectory(fileAbsolutePath);
|
||
|
const suppressionsPath = path_1.default.join(eslintrcDirectory, '.eslint-bulk-suppressions.json');
|
||
|
let suppressionsJson = { suppressions: [] };
|
||
|
try {
|
||
|
const fileContent = fs_1.default.readFileSync(suppressionsPath, 'utf-8');
|
||
|
suppressionsJson = JSON.parse(fileContent);
|
||
|
if (!validateSuppressionsJson(suppressionsJson)) {
|
||
|
console.warn(`Unexpected file content in .eslint-bulk-suppressions.json. JSON expected to be in the following format:
|
||
|
{
|
||
|
suppressions: {
|
||
|
file: string;
|
||
|
scopeId: string;
|
||
|
rule: string;
|
||
|
}[];
|
||
|
}
|
||
|
Please check file content, or delete file if suppressions are no longer needed.
|
||
|
`);
|
||
|
suppressionsJson = { suppressions: [] };
|
||
|
}
|
||
|
}
|
||
|
catch (_a) {
|
||
|
// Do nothing and let JSON.parse() log the error. suppressionsJson will stay as the initialized value.
|
||
|
}
|
||
|
return suppressionsJson;
|
||
|
}
|
||
|
function shouldWriteSuppression(fileAbsolutePath, suppression) {
|
||
|
if (process.env.ESLINT_BULK_SUPPRESS === undefined)
|
||
|
return false;
|
||
|
if (isSuppressed(fileAbsolutePath, suppression))
|
||
|
return false;
|
||
|
const rulesToSuppress = process.env.ESLINT_BULK_SUPPRESS.split(',');
|
||
|
if (rulesToSuppress.length === 1 && rulesToSuppress[0] === '*')
|
||
|
return true;
|
||
|
return rulesToSuppress.includes(suppression.rule);
|
||
|
}
|
||
|
function isSuppressed(fileAbsolutePath, suppression) {
|
||
|
const suppressionsJson = readSuppressionsJson(fileAbsolutePath);
|
||
|
return (suppressionsJson.suppressions.find((element) => element.file === suppression.file &&
|
||
|
element.scopeId === suppression.scopeId &&
|
||
|
element.rule === suppression.rule) !== undefined);
|
||
|
}
|
||
|
function insort(array, item, compareFunction) {
|
||
|
const index = array.findIndex((element) => compareFunction(element, item) > 0);
|
||
|
if (index === -1)
|
||
|
array.push(item);
|
||
|
else
|
||
|
array.splice(index, 0, item);
|
||
|
}
|
||
|
function compareSuppressions(a, b) {
|
||
|
if (a.file < b.file)
|
||
|
return -1;
|
||
|
if (a.file > b.file)
|
||
|
return 1;
|
||
|
if (a.scopeId < b.scopeId)
|
||
|
return -1;
|
||
|
if (a.scopeId > b.scopeId)
|
||
|
return 1;
|
||
|
if (a.rule < b.rule)
|
||
|
return -1;
|
||
|
if (a.rule > b.rule)
|
||
|
return 1;
|
||
|
return 0;
|
||
|
}
|
||
|
function writeSuppressionToFile(fileAbsolutePath, suppression) {
|
||
|
const eslintrcDirectory = findEslintrcDirectory(fileAbsolutePath);
|
||
|
const suppressionsJson = readSuppressionsJson(fileAbsolutePath);
|
||
|
insort(suppressionsJson.suppressions, suppression, compareSuppressions);
|
||
|
const suppressionsPath = path_1.default.join(eslintrcDirectory, '.eslint-bulk-suppressions.json');
|
||
|
fs_1.default.writeFileSync(suppressionsPath, JSON.stringify(suppressionsJson, null, 2));
|
||
|
}
|
||
|
const usedSuppressions = new Set();
|
||
|
function serializeSuppression(fileAbsolutePath, suppression) {
|
||
|
return `${fileAbsolutePath}|${suppression.file}|${suppression.scopeId}|${suppression.rule}`;
|
||
|
}
|
||
|
function deserializeSuppression(serializedSuppression) {
|
||
|
const [file, scopeId, rule] = serializedSuppression.split('|');
|
||
|
return { file, scopeId, rule };
|
||
|
}
|
||
|
// One-line insert into the ruleContext report method to prematurely exit if the ESLint problem has been suppressed
|
||
|
function shouldBulkSuppress(params) {
|
||
|
// Use this ENV variable to turn off eslint-bulk-suppressions functionality, default behavior is on
|
||
|
if (process.env.ESLINT_BULK_ENABLE === 'false')
|
||
|
return false;
|
||
|
const { filename: fileAbsolutePath, currentNode, ruleId: rule } = params;
|
||
|
const eslintrcDirectory = findEslintrcDirectory(fileAbsolutePath);
|
||
|
const fileRelativePath = path_1.default.relative(eslintrcDirectory, fileAbsolutePath);
|
||
|
const scopeId = calculateScopeId(currentNode);
|
||
|
const suppression = { file: fileRelativePath, scopeId, rule };
|
||
|
if (shouldWriteSuppression(fileAbsolutePath, suppression)) {
|
||
|
writeSuppressionToFile(fileAbsolutePath, suppression);
|
||
|
}
|
||
|
const shouldBulkSuppress = isSuppressed(fileAbsolutePath, suppression);
|
||
|
if (shouldBulkSuppress) {
|
||
|
usedSuppressions.add(serializeSuppression(fileAbsolutePath, suppression));
|
||
|
}
|
||
|
return shouldBulkSuppress;
|
||
|
}
|
||
|
exports.shouldBulkSuppress = shouldBulkSuppress;
|
||
|
function onFinish(params) {
|
||
|
if (process.env.ESLINT_BULK_PRUNE === 'true') {
|
||
|
BulkSuppressionsPrune(params);
|
||
|
}
|
||
|
}
|
||
|
exports.onFinish = onFinish;
|
||
|
function BulkSuppressionsPrune(params) {
|
||
|
const { filename: fileAbsolutePath } = params;
|
||
|
const suppressionsJson = readSuppressionsJson(fileAbsolutePath);
|
||
|
const newSuppressionsJson = {
|
||
|
suppressions: suppressionsJson.suppressions.filter((suppression) => {
|
||
|
return usedSuppressions.has(serializeSuppression(fileAbsolutePath, suppression));
|
||
|
})
|
||
|
};
|
||
|
const eslintrcDirectory = findEslintrcDirectory(fileAbsolutePath);
|
||
|
const suppressionsPath = path_1.default.join(eslintrcDirectory, '.eslint-bulk-suppressions.json');
|
||
|
fs_1.default.writeFileSync(suppressionsPath, JSON.stringify(newSuppressionsJson, null, 2));
|
||
|
}
|
||
|
exports.BulkSuppressionsPrune = BulkSuppressionsPrune;
|
||
|
// utility function for linter-patch.js to make require statements that use relative paths in linter.js work in linter-patch.js
|
||
|
function requireFromPathToLinterJS(importPath) {
|
||
|
if (!_patch_base_1.eslintFolder) {
|
||
|
return require(importPath);
|
||
|
}
|
||
|
const pathToLinterFolder = path_1.default.join(_patch_base_1.eslintFolder, 'lib', 'linter');
|
||
|
const moduleAbsolutePath = require.resolve(importPath, { paths: [pathToLinterFolder] });
|
||
|
return require(moduleAbsolutePath);
|
||
|
}
|
||
|
exports.requireFromPathToLinterJS = requireFromPathToLinterJS;
|
||
|
function patchClass(originalClass, patchedClass) {
|
||
|
// Get all the property names of the patched class prototype
|
||
|
let patchedProperties = Object.getOwnPropertyNames(patchedClass.prototype);
|
||
|
// Loop through all the properties
|
||
|
for (let prop of patchedProperties) {
|
||
|
// Override the property in the original class
|
||
|
originalClass.prototype[prop] = patchedClass.prototype[prop];
|
||
|
}
|
||
|
// Handle getters and setters
|
||
|
let descriptors = Object.getOwnPropertyDescriptors(patchedClass.prototype);
|
||
|
for (let prop in descriptors) {
|
||
|
let descriptor = descriptors[prop];
|
||
|
if (descriptor.get || descriptor.set) {
|
||
|
Object.defineProperty(originalClass.prototype, prop, descriptor);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
exports.patchClass = patchClass;
|
||
|
//# sourceMappingURL=bulk-suppressions-patch.js.map
|