diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3e6c3864..37704c8e30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Refactor] improve performance by avoiding unnecessary `Components.detect` ([#3273][] @golopot) * [Refactor] add `isParenthesized` AST util ([#3203][] @Belco90) * [Docs] `default-props-match-prop-types`, `require-default-props`, `sort-prop-types`: fix typos ([#3279][] @nix6839) +* [Refactor] improve performance of rule merging ([#3281][] @golopot) +[#3281]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3281 [#3280]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3280 [#3279]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3279 [#3273]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3273 diff --git a/lib/util/Components.js b/lib/util/Components.js index 4b545fa75a..7d6f946616 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -247,6 +247,39 @@ function getWrapperFunctions(context, pragma) { ]); } +// eslint-disable-next-line valid-jsdoc +/** + * Merge many eslint rules into one + * @param {{[_: string]: Function}[]} rules the returned values for eslint rule.create(context) + * @returns {{[_: string]: Function}} merged rule + */ +function mergeRules(rules) { + /** @type {Map} */ + const handlersByKey = new Map(); + rules.forEach((rule) => { + Object.keys(rule).forEach((key) => { + const fns = handlersByKey.get(key); + if (!fns) { + handlersByKey.set(key, [rule[key]]); + } else { + fns.push(rule[key]); + } + }); + }); + + /** @type {{[key: string]: Function}} */ + const rule = {}; + handlersByKey.forEach((fns, key) => { + rule[key] = function mergedHandler(node) { + fns.forEach((fn) => { + fn(node); + }); + }; + }); + + return rule; +} + function componentRule(rule, context) { const pragma = pragmaUtil.getFromContext(context); const sourceCode = context.getSourceCode(); @@ -859,44 +892,21 @@ function componentRule(rule, context) { }, }; - // Update the provided rule instructions to add the component detection const ruleInstructions = rule(context, components, utils); - const updatedRuleInstructions = Object.assign({}, ruleInstructions); const propTypesInstructions = propTypesUtil(context, components, utils); const usedPropTypesInstructions = usedPropTypesUtil(context, components, utils); const defaultPropsInstructions = defaultPropsUtil(context, components, utils); - const allKeys = new Set(Object.keys(detectionInstructions).concat( - Object.keys(propTypesInstructions), - Object.keys(usedPropTypesInstructions), - Object.keys(defaultPropsInstructions), - Object.keys(reactImportInstructions) - )); - allKeys.forEach((instruction) => { - updatedRuleInstructions[instruction] = (node) => { - if (instruction in detectionInstructions) { - detectionInstructions[instruction](node); - } - if (instruction in propTypesInstructions) { - propTypesInstructions[instruction](node); - } - if (instruction in usedPropTypesInstructions) { - usedPropTypesInstructions[instruction](node); - } - if (instruction in defaultPropsInstructions) { - defaultPropsInstructions[instruction](node); - } - if (instruction in reactImportInstructions) { - reactImportInstructions[instruction](node); - } - if (ruleInstructions[instruction]) { - return ruleInstructions[instruction](node); - } - }; - }); + const mergedRule = mergeRules([ + detectionInstructions, + propTypesInstructions, + usedPropTypesInstructions, + defaultPropsInstructions, + reactImportInstructions, + ruleInstructions, + ]); - // Return the updated rule instructions - return updatedRuleInstructions; + return mergedRule; } module.exports = Object.assign(Components, {