bulk-suppressions-patch.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. "use strict";
  2. // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
  3. // See LICENSE in the project root for license information.
  4. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  5. if (k2 === undefined) k2 = k;
  6. var desc = Object.getOwnPropertyDescriptor(m, k);
  7. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  8. desc = { enumerable: true, get: function() { return m[k]; } };
  9. }
  10. Object.defineProperty(o, k2, desc);
  11. }) : (function(o, m, k, k2) {
  12. if (k2 === undefined) k2 = k;
  13. o[k2] = m[k];
  14. }));
  15. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  16. Object.defineProperty(o, "default", { enumerable: true, value: v });
  17. }) : function(o, v) {
  18. o["default"] = v;
  19. });
  20. var __importStar = (this && this.__importStar) || (function () {
  21. var ownKeys = function(o) {
  22. ownKeys = Object.getOwnPropertyNames || function (o) {
  23. var ar = [];
  24. for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
  25. return ar;
  26. };
  27. return ownKeys(o);
  28. };
  29. return function (mod) {
  30. if (mod && mod.__esModule) return mod;
  31. var result = {};
  32. if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
  33. __setModuleDefault(result, mod);
  34. return result;
  35. };
  36. })();
  37. var __importDefault = (this && this.__importDefault) || function (mod) {
  38. return (mod && mod.__esModule) ? mod : { "default": mod };
  39. };
  40. Object.defineProperty(exports, "__esModule", { value: true });
  41. exports.shouldBulkSuppress = shouldBulkSuppress;
  42. exports.prune = prune;
  43. exports.write = write;
  44. exports.requireFromPathToLinterJS = requireFromPathToLinterJS;
  45. exports.patchClass = patchClass;
  46. exports.extendVerifyFunction = extendVerifyFunction;
  47. const fs_1 = __importDefault(require("fs"));
  48. const Guards = __importStar(require("./ast-guards"));
  49. const _patch_base_1 = require("../_patch-base");
  50. const constants_1 = require("./constants");
  51. const bulk_suppressions_file_1 = require("./bulk-suppressions-file");
  52. const ESLINT_CONFIG_FILENAMES = [
  53. 'eslint.config.js',
  54. 'eslint.config.cjs',
  55. 'eslint.config.mjs',
  56. '.eslintrc.js',
  57. '.eslintrc.cjs'
  58. // Several other filenames are allowed, but this patch requires that it be loaded via a JS config file,
  59. // so we only need to check for the JS-based filenames
  60. ];
  61. const SUPPRESSION_SYMBOL = Symbol('suppression');
  62. const ESLINT_BULK_SUPPRESS_ENV_VAR_VALUE = process.env[constants_1.ESLINT_BULK_SUPPRESS_ENV_VAR_NAME];
  63. const SUPPRESS_ALL_RULES = ESLINT_BULK_SUPPRESS_ENV_VAR_VALUE === '*';
  64. const RULES_TO_SUPPRESS = ESLINT_BULK_SUPPRESS_ENV_VAR_VALUE
  65. ? new Set(ESLINT_BULK_SUPPRESS_ENV_VAR_VALUE.split(','))
  66. : undefined;
  67. function getNodeName(node) {
  68. if (Guards.isClassDeclarationWithName(node)) {
  69. return node.id.name;
  70. }
  71. else if (Guards.isFunctionDeclarationWithName(node)) {
  72. return node.id.name;
  73. }
  74. else if (Guards.isClassExpressionWithName(node)) {
  75. return node.id.name;
  76. }
  77. else if (Guards.isFunctionExpressionWithName(node)) {
  78. return node.id.name;
  79. }
  80. else if (Guards.isNormalVariableDeclaratorWithAnonymousExpressionAssigned(node)) {
  81. return node.id.name;
  82. }
  83. else if (Guards.isNormalObjectPropertyWithAnonymousExpressionAssigned(node)) {
  84. return node.key.name;
  85. }
  86. else if (Guards.isNormalClassPropertyDefinitionWithAnonymousExpressionAssigned(node)) {
  87. return node.key.name;
  88. }
  89. else if (Guards.isNormalAssignmentPatternWithAnonymousExpressionAssigned(node)) {
  90. return node.left.name;
  91. }
  92. else if (Guards.isNormalMethodDefinition(node)) {
  93. return node.key.name;
  94. }
  95. else if (Guards.isTSEnumDeclaration(node)) {
  96. return node.id.name;
  97. }
  98. else if (Guards.isTSInterfaceDeclaration(node)) {
  99. return node.id.name;
  100. }
  101. else if (Guards.isTSTypeAliasDeclaration(node)) {
  102. return node.id.name;
  103. }
  104. }
  105. function calculateScopeId(node) {
  106. const scopeIds = [];
  107. for (let current = node; current; current = current.parent) {
  108. const scopeIdForASTNode = getNodeName(current);
  109. if (scopeIdForASTNode !== undefined) {
  110. scopeIds.unshift(scopeIdForASTNode);
  111. }
  112. }
  113. if (scopeIds.length === 0) {
  114. return '.';
  115. }
  116. else {
  117. return '.' + scopeIds.join('.');
  118. }
  119. }
  120. const eslintConfigPathByFileOrFolderPath = new Map();
  121. function findEslintConfigFolderPathForNormalizedFileAbsolutePath(normalizedFilePath) {
  122. const cachedFolderPathForFilePath = eslintConfigPathByFileOrFolderPath.get(normalizedFilePath);
  123. if (cachedFolderPathForFilePath) {
  124. return cachedFolderPathForFilePath;
  125. }
  126. const normalizedFileFolderPath = normalizedFilePath.substring(0, normalizedFilePath.lastIndexOf('/'));
  127. const pathsToCache = [normalizedFilePath];
  128. let eslintConfigFolderPath;
  129. findEslintConfigFileLoop: for (let currentFolder = normalizedFileFolderPath; currentFolder; // 'something'.substring(0, -1) is ''
  130. currentFolder = currentFolder.substring(0, currentFolder.lastIndexOf('/'))) {
  131. const cachedEslintrcFolderPath = eslintConfigPathByFileOrFolderPath.get(currentFolder);
  132. if (cachedEslintrcFolderPath) {
  133. // Need to cache this result into the intermediate paths
  134. eslintConfigFolderPath = cachedEslintrcFolderPath;
  135. break;
  136. }
  137. pathsToCache.push(currentFolder);
  138. for (const eslintConfigFilename of ESLINT_CONFIG_FILENAMES) {
  139. if (fs_1.default.existsSync(`${currentFolder}/${eslintConfigFilename}`)) {
  140. eslintConfigFolderPath = currentFolder;
  141. break findEslintConfigFileLoop;
  142. }
  143. }
  144. }
  145. if (eslintConfigFolderPath) {
  146. for (const checkedFolder of pathsToCache) {
  147. eslintConfigPathByFileOrFolderPath.set(checkedFolder, eslintConfigFolderPath);
  148. }
  149. return eslintConfigFolderPath;
  150. }
  151. else {
  152. throw new Error(`Cannot locate an ESLint configuration file for ${normalizedFilePath}`);
  153. }
  154. }
  155. // One-line insert into the ruleContext report method to prematurely exit if the ESLint problem has been suppressed
  156. function shouldBulkSuppress(params) {
  157. // Use this ENV variable to turn off eslint-bulk-suppressions functionality, default behavior is on
  158. if (process.env[constants_1.ESLINT_BULK_ENABLE_ENV_VAR_NAME] === 'false') {
  159. return false;
  160. }
  161. const { filename: fileAbsolutePath, currentNode, ruleId: rule, problem } = params;
  162. const normalizedFileAbsolutePath = fileAbsolutePath.replace(/\\/g, '/');
  163. const eslintConfigDirectory = findEslintConfigFolderPathForNormalizedFileAbsolutePath(normalizedFileAbsolutePath);
  164. const fileRelativePath = normalizedFileAbsolutePath.substring(eslintConfigDirectory.length + 1);
  165. const scopeId = calculateScopeId(currentNode);
  166. const suppression = { file: fileRelativePath, scopeId, rule };
  167. const config = (0, bulk_suppressions_file_1.getSuppressionsConfigForEslintConfigFolderPath)(eslintConfigDirectory);
  168. const serializedSuppression = (0, bulk_suppressions_file_1.serializeSuppression)(suppression);
  169. const currentNodeIsSuppressed = config.serializedSuppressions.has(serializedSuppression);
  170. if (currentNodeIsSuppressed || SUPPRESS_ALL_RULES || (RULES_TO_SUPPRESS === null || RULES_TO_SUPPRESS === void 0 ? void 0 : RULES_TO_SUPPRESS.has(suppression.rule))) {
  171. problem[SUPPRESSION_SYMBOL] = {
  172. suppression,
  173. serializedSuppression,
  174. config
  175. };
  176. }
  177. return process.env[constants_1.ESLINT_BULK_PRUNE_ENV_VAR_NAME] !== '1' && currentNodeIsSuppressed;
  178. }
  179. function prune() {
  180. for (const [eslintConfigFolderPath, suppressionsConfig] of (0, bulk_suppressions_file_1.getAllBulkSuppressionsConfigsByEslintConfigFolderPath)()) {
  181. if (suppressionsConfig) {
  182. const { newSerializedSuppressions, newJsonObject } = suppressionsConfig;
  183. const newSuppressionsConfig = {
  184. serializedSuppressions: newSerializedSuppressions,
  185. jsonObject: newJsonObject,
  186. newSerializedSuppressions: new Set(),
  187. newJsonObject: { suppressions: [] }
  188. };
  189. (0, bulk_suppressions_file_1.writeSuppressionsJsonToFile)(eslintConfigFolderPath, newSuppressionsConfig);
  190. }
  191. }
  192. }
  193. function write() {
  194. for (const [eslintrcFolderPath, suppressionsConfig] of (0, bulk_suppressions_file_1.getAllBulkSuppressionsConfigsByEslintConfigFolderPath)()) {
  195. if (suppressionsConfig) {
  196. (0, bulk_suppressions_file_1.writeSuppressionsJsonToFile)(eslintrcFolderPath, suppressionsConfig);
  197. }
  198. }
  199. }
  200. // utility function for linter-patch.js to make require statements that use relative paths in linter.js work in linter-patch.js
  201. function requireFromPathToLinterJS(importPath) {
  202. if (!_patch_base_1.eslintFolder) {
  203. return require(importPath);
  204. }
  205. const pathToLinterFolder = `${_patch_base_1.eslintFolder}/lib/linter`;
  206. const moduleAbsolutePath = require.resolve(importPath, { paths: [pathToLinterFolder] });
  207. return require(moduleAbsolutePath);
  208. }
  209. function patchClass(originalClass, patchedClass) {
  210. // Get all the property names of the patched class prototype
  211. const patchedProperties = Object.getOwnPropertyNames(patchedClass.prototype);
  212. // Loop through all the properties
  213. for (const prop of patchedProperties) {
  214. // Override the property in the original class
  215. originalClass.prototype[prop] = patchedClass.prototype[prop];
  216. }
  217. // Handle getters and setters
  218. for (const [prop, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(patchedClass.prototype))) {
  219. if (descriptor.get || descriptor.set) {
  220. Object.defineProperty(originalClass.prototype, prop, descriptor);
  221. }
  222. }
  223. }
  224. /**
  225. * This returns a wrapped version of the "verify" function from ESLint's Linter class
  226. * that postprocesses rule violations that weren't suppressed by comments. This postprocessing
  227. * records suppressions that weren't otherwise suppressed by comments to be used
  228. * by the "suppress" and "prune" commands.
  229. */
  230. function extendVerifyFunction(originalFn) {
  231. return function (...args) {
  232. const problems = originalFn.apply(this, args);
  233. if (problems) {
  234. for (const problem of problems) {
  235. if (problem[SUPPRESSION_SYMBOL]) {
  236. const { serializedSuppression, suppression, config: { newSerializedSuppressions, jsonObject: { suppressions }, newJsonObject: { suppressions: newSuppressions } } } = problem[SUPPRESSION_SYMBOL];
  237. if (!newSerializedSuppressions.has(serializedSuppression)) {
  238. newSerializedSuppressions.add(serializedSuppression);
  239. newSuppressions.push(suppression);
  240. suppressions.push(suppression);
  241. }
  242. }
  243. }
  244. }
  245. return problems;
  246. };
  247. }
  248. //# sourceMappingURL=bulk-suppressions-patch.js.map