From f82e56e9acfb9562ece76441472d5657d7d5e296 Mon Sep 17 00:00:00 2001 From: moonlightaria <72459063+moonlightaria@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:14:32 -0400 Subject: [PATCH] perf: various performance improvements (#17135) * perf: addParensIndent replaced shifting with reverse iteration * perf: changed string handling from regex to manual parsing * refactor: more explicit * perf: remove unnessisary array and object creation * pref: opimized out reduce for basic iteration * perf: replaced reduce with basic iteration * perf: optimized unessisary array creation and objects * perf: replaced reduce with basic iteration * perf: replaced reduce with basic iteration * perf: replaced filter with basic loop for counting valid elements * perf: removed array creation in indent program exit --- lib/cli-engine/cli-engine.js | 45 ++++++++++------- lib/eslint/flat-eslint.js | 44 ++++++++++------- lib/rules/accessor-pairs.js | 74 +++++++++++++--------------- lib/rules/array-element-newline.js | 14 ++++-- lib/rules/grouped-accessor-pairs.js | 75 +++++++++++++---------------- lib/rules/indent.js | 74 ++++++++++++++++------------ lib/rules/max-len.js | 30 +++++++----- lib/rules/no-loss-of-precision.js | 20 +++++--- 8 files changed, 203 insertions(+), 173 deletions(-) diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js index 093a20b1ded..311dc61e81c 100644 --- a/lib/cli-engine/cli-engine.js +++ b/lib/cli-engine/cli-engine.js @@ -158,7 +158,17 @@ function validateFixTypes(fixTypes) { * @private */ function calculateStatsPerFile(messages) { - return messages.reduce((stat, message) => { + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + if (message.fatal || message.severity === 2) { stat.errorCount++; if (message.fatal) { @@ -173,14 +183,8 @@ function calculateStatsPerFile(messages) { stat.fixableWarningCount++; } } - return stat; - }, { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }); + } + return stat; } /** @@ -190,20 +194,25 @@ function calculateStatsPerFile(messages) { * @private */ function calculateStatsPerRun(results) { - return results.reduce((stat, result) => { - stat.errorCount += result.errorCount; - stat.fatalErrorCount += result.fatalErrorCount; - stat.warningCount += result.warningCount; - stat.fixableErrorCount += result.fixableErrorCount; - stat.fixableWarningCount += result.fixableWarningCount; - return stat; - }, { + const stat = { errorCount: 0, fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 - }); + }; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + stat.errorCount += result.errorCount; + stat.fatalErrorCount += result.fatalErrorCount; + stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; + } + + return stat; } /** diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js index f615ae17155..3245808c2af 100644 --- a/lib/eslint/flat-eslint.js +++ b/lib/eslint/flat-eslint.js @@ -103,7 +103,17 @@ const importedConfigFileModificationTime = new Map(); * @private */ function calculateStatsPerFile(messages) { - return messages.reduce((stat, message) => { + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 + }; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + if (message.fatal || message.severity === 2) { stat.errorCount++; if (message.fatal) { @@ -118,14 +128,8 @@ function calculateStatsPerFile(messages) { stat.fixableWarningCount++; } } - return stat; - }, { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }); + } + return stat; } /** @@ -135,20 +139,24 @@ function calculateStatsPerFile(messages) { * @private */ function calculateStatsPerRun(results) { - return results.reduce((stat, result) => { - stat.errorCount += result.errorCount; - stat.fatalErrorCount += result.fatalErrorCount; - stat.warningCount += result.warningCount; - stat.fixableErrorCount += result.fixableErrorCount; - stat.fixableWarningCount += result.fixableWarningCount; - return stat; - }, { + const stat = { errorCount: 0, fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 - }); + }; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + stat.errorCount += result.errorCount; + stat.fatalErrorCount += result.fatalErrorCount; + stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; + } + return stat; } /** diff --git a/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js index 03b51e461c0..f97032895df 100644 --- a/lib/rules/accessor-pairs.js +++ b/lib/rules/accessor-pairs.js @@ -223,43 +223,6 @@ module.exports = { } } - /** - * Creates a new `AccessorData` object for the given getter or setter node. - * @param {ASTNode} node A getter or setter node. - * @returns {AccessorData} New `AccessorData` object that contains the given node. - * @private - */ - function createAccessorData(node) { - const name = astUtils.getStaticPropertyName(node); - const key = (name !== null) ? name : sourceCode.getTokens(node.key); - - return { - key, - getters: node.kind === "get" ? [node] : [], - setters: node.kind === "set" ? [node] : [] - }; - } - - /** - * Merges the given `AccessorData` object into the given accessors list. - * @param {AccessorData[]} accessors The list to merge into. - * @param {AccessorData} accessorData The object to merge. - * @returns {AccessorData[]} The same instance with the merged object. - * @private - */ - function mergeAccessorData(accessors, accessorData) { - const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); - - if (equalKeyElement) { - equalKeyElement.getters.push(...accessorData.getters); - equalKeyElement.setters.push(...accessorData.setters); - } else { - accessors.push(accessorData); - } - - return accessors; - } - /** * Checks accessor pairs in the given list of nodes. * @param {ASTNode[]} nodes The list to check. @@ -267,10 +230,39 @@ module.exports = { * @private */ function checkList(nodes) { - const accessors = nodes - .filter(isAccessorKind) - .map(createAccessorData) - .reduce(mergeAccessorData, []); + const accessors = []; + let found = false; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (isAccessorKind(node)) { + + // Creates a new `AccessorData` object for the given getter or setter node. + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); + + // Merges the given `AccessorData` object into the given accessors list. + for (let j = 0; j < accessors.length; j++) { + const accessor = accessors[j]; + + if (areEqualKeys(accessor.key, key)) { + accessor.getters.push(...node.kind === "get" ? [node] : []); + accessor.setters.push(...node.kind === "set" ? [node] : []); + found = true; + break; + } + } + if (!found) { + accessors.push({ + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }); + } + found = false; + } + } for (const { getters, setters } of accessors) { if (checkSetWithoutGet && setters.length && !getters.length) { diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js index be547ec3617..0c806ef3a82 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -240,19 +240,25 @@ module.exports = { .some(element => element.loc.start.line !== element.loc.end.line); } - const linebreaksCount = node.elements.map((element, i) => { + let linebreaksCount = 0; + + for (let i = 0; i < node.elements.length; i++) { + const element = node.elements[i]; + const previousElement = elements[i - 1]; if (i === 0 || element === null || previousElement === null) { - return false; + continue; } const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); - return !astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement); - }).filter(isBreak => isBreak === true).length; + if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { + linebreaksCount++; + } + } const needsLinebreaks = ( elements.length >= options.minItems || diff --git a/lib/rules/grouped-accessor-pairs.js b/lib/rules/grouped-accessor-pairs.js index c08e1c4973e..9556f475682 100644 --- a/lib/rules/grouped-accessor-pairs.js +++ b/lib/rules/grouped-accessor-pairs.js @@ -137,43 +137,6 @@ module.exports = { }); } - /** - * Creates a new `AccessorData` object for the given getter or setter node. - * @param {ASTNode} node A getter or setter node. - * @returns {AccessorData} New `AccessorData` object that contains the given node. - * @private - */ - function createAccessorData(node) { - const name = astUtils.getStaticPropertyName(node); - const key = (name !== null) ? name : sourceCode.getTokens(node.key); - - return { - key, - getters: node.kind === "get" ? [node] : [], - setters: node.kind === "set" ? [node] : [] - }; - } - - /** - * Merges the given `AccessorData` object into the given accessors list. - * @param {AccessorData[]} accessors The list to merge into. - * @param {AccessorData} accessorData The object to merge. - * @returns {AccessorData[]} The same instance with the merged object. - * @private - */ - function mergeAccessorData(accessors, accessorData) { - const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key)); - - if (equalKeyElement) { - equalKeyElement.getters.push(...accessorData.getters); - equalKeyElement.setters.push(...accessorData.setters); - } else { - accessors.push(accessorData); - } - - return accessors; - } - /** * Checks accessor pairs in the given list of nodes. * @param {ASTNode[]} nodes The list to check. @@ -182,11 +145,39 @@ module.exports = { * @private */ function checkList(nodes, shouldCheck) { - const accessors = nodes - .filter(shouldCheck) - .filter(isAccessorKind) - .map(createAccessorData) - .reduce(mergeAccessorData, []); + const accessors = []; + let found = false; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (shouldCheck(node) && isAccessorKind(node)) { + + // Creates a new `AccessorData` object for the given getter or setter node. + const name = astUtils.getStaticPropertyName(node); + const key = (name !== null) ? name : sourceCode.getTokens(node.key); + + // Merges the given `AccessorData` object into the given accessors list. + for (let j = 0; j < accessors.length; j++) { + const accessor = accessors[j]; + + if (areEqualKeys(accessor.key, key)) { + accessor.getters.push(...node.kind === "get" ? [node] : []); + accessor.setters.push(...node.kind === "set" ? [node] : []); + found = true; + break; + } + } + if (!found) { + accessors.push({ + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [] + }); + } + found = false; + } + } for (const { getters, setters } of accessors) { diff --git a/lib/rules/indent.js b/lib/rules/indent.js index bcc5143d26e..9068006d497 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -188,15 +188,19 @@ class TokenInfo { */ constructor(sourceCode) { this.sourceCode = sourceCode; - this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce((map, token) => { - if (!map.has(token.loc.start.line)) { - map.set(token.loc.start.line, token); + this.firstTokensByLineNumber = new Map(); + const tokens = sourceCode.tokensAndComments; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (!this.firstTokensByLineNumber.has(token.loc.start.line)) { + this.firstTokensByLineNumber.set(token.loc.start.line, token); } - if (!map.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) { - map.set(token.loc.end.line, token); + if (!this.firstTokensByLineNumber.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) { + this.firstTokensByLineNumber.set(token.loc.end.line, token); } - return map; - }, new Map()); + } } /** @@ -964,19 +968,19 @@ module.exports = { const parenStack = []; const parenPairs = []; - tokens.forEach(nextToken => { + for (let i = 0; i < tokens.length; i++) { + const nextToken = tokens[i]; - // Accumulate a list of parenthesis pairs if (astUtils.isOpeningParenToken(nextToken)) { parenStack.push(nextToken); } else if (astUtils.isClosingParenToken(nextToken)) { - parenPairs.unshift({ left: parenStack.pop(), right: nextToken }); + parenPairs.push({ left: parenStack.pop(), right: nextToken }); } - }); + } - parenPairs.forEach(pair => { - const leftParen = pair.left; - const rightParen = pair.right; + for (let i = parenPairs.length - 1; i >= 0; i--) { + const leftParen = parenPairs[i].left; + const rightParen = parenPairs[i].right; // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. if (!parameterParens.has(leftParen) && !parameterParens.has(rightParen)) { @@ -990,7 +994,7 @@ module.exports = { } offsets.setDesiredOffset(rightParen, leftParen, 0); - }); + } } /** @@ -1711,9 +1715,13 @@ module.exports = { } // Invoke the queued offset listeners for the nodes that aren't ignored. - listenerCallQueue - .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node)) - .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node)); + for (let i = 0; i < listenerCallQueue.length; i++) { + const nodeInfo = listenerCallQueue[i]; + + if (!ignoredNodes.has(nodeInfo.node)) { + nodeInfo.listener(nodeInfo.node); + } + } // Update the offsets for ignored nodes to prevent their child tokens from being reported. ignoredNodes.forEach(ignoreNode); @@ -1724,27 +1732,31 @@ module.exports = { * Create a Map from (tokenOrComment) => (precedingToken). * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. */ - const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => { + const precedingTokens = new WeakMap(); + + for (let i = 0; i < sourceCode.ast.comments.length; i++) { + const comment = sourceCode.ast.comments[i]; + const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); + const hasToken = precedingTokens.has(tokenOrCommentBefore) ? precedingTokens.get(tokenOrCommentBefore) : tokenOrCommentBefore; - return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore); - }, new WeakMap()); + precedingTokens.set(comment, hasToken); + } - sourceCode.lines.forEach((line, lineIndex) => { - const lineNumber = lineIndex + 1; + for (let i = 1; i < sourceCode.lines.length + 1; i++) { - if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) { + if (!tokenInfo.firstTokensByLineNumber.has(i)) { // Don't check indentation on blank lines - return; + continue; } - const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber); + const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(i); - if (firstTokenOfLine.loc.start.line !== lineNumber) { + if (firstTokenOfLine.loc.start.line !== i) { // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. - return; + continue; } if (astUtils.isCommentToken(firstTokenOfLine)) { @@ -1769,18 +1781,18 @@ module.exports = { mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) || mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter)) ) { - return; + continue; } } // If the token matches the expected indentation, don't report it. if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) { - return; + continue; } // Otherwise, report the token/comment. report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); - }); + } } } ); diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 59e85214a29..53ad5310799 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -252,19 +252,23 @@ module.exports = { return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression"); } - /** - * A reducer to group an AST node by line number, both start and end. - * @param {Object} acc the accumulator - * @param {ASTNode} node the AST node in question - * @returns {Object} the modified accumulator - * @private + * + * reduce an array of AST nodes by line number, both start and end. + * @param {ASTNode[]} arr array of AST nodes + * @returns {Object} accululated AST nodes */ - function groupByLineNumber(acc, node) { - for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) { - ensureArrayAndPush(acc, i, node); + function groupArrayByLineNumber(arr) { + const obj = {}; + + for (let i = 0; i < arr.length; i++) { + const node = arr[i]; + + for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) { + ensureArrayAndPush(obj, j, node); + } } - return acc; + return obj; } /** @@ -312,13 +316,13 @@ module.exports = { let commentsIndex = 0; const strings = getAllStrings(); - const stringsByLine = strings.reduce(groupByLineNumber, {}); + const stringsByLine = groupArrayByLineNumber(strings); const templateLiterals = getAllTemplateLiterals(); - const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {}); + const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals); const regExpLiterals = getAllRegExpLiterals(); - const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {}); + const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals); lines.forEach((line, i) => { diff --git a/lib/rules/no-loss-of-precision.js b/lib/rules/no-loss-of-precision.js index 22ca7f93e3a..b3635e3d509 100644 --- a/lib/rules/no-loss-of-precision.js +++ b/lib/rules/no-loss-of-precision.js @@ -83,7 +83,7 @@ module.exports = { * @returns {string} the numeric string with a decimal point in the proper place */ function addDecimalPointToNumber(stringNumber) { - return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`; + return `${stringNumber[0]}.${stringNumber.slice(1)}`; } /** @@ -92,7 +92,12 @@ module.exports = { * @returns {string} the stripped string */ function removeLeadingZeros(numberAsString) { - return numberAsString.replace(/^0*/u, ""); + for (let i = 0; i < numberAsString.length; i++) { + if (numberAsString[i] !== "0") { + return numberAsString.slice(i); + } + } + return numberAsString; } /** @@ -101,7 +106,12 @@ module.exports = { * @returns {string} the stripped string */ function removeTrailingZeros(numberAsString) { - return numberAsString.replace(/0*$/u, ""); + for (let i = numberAsString.length - 1; i >= 0; i--) { + if (numberAsString[i] !== "0") { + return numberAsString.slice(0, i + 1); + } + } + return numberAsString; } /** @@ -128,7 +138,7 @@ module.exports = { const trimmedFloat = removeLeadingZeros(stringFloat); if (trimmedFloat.startsWith(".")) { - const decimalDigits = trimmedFloat.split(".").pop(); + const decimalDigits = trimmedFloat.slice(1); const significantDigits = removeLeadingZeros(decimalDigits); return { @@ -144,7 +154,6 @@ module.exports = { }; } - /** * Converts a base ten number to proper scientific notation * @param {string} stringNumber the string representation of the base ten number to be converted @@ -160,7 +169,6 @@ module.exports = { : normalizedNumber.magnitude; return `${normalizedCoefficient}e${magnitude}`; - } /**