diff --git a/lib/ast-utils.js b/lib/ast-utils.js index 38f6cc9617e..4f23e5ff688 100644 --- a/lib/ast-utils.js +++ b/lib/ast-utils.js @@ -290,8 +290,23 @@ const lineIndexCache = new WeakMap(); function getLineIndices(sourceCode) { if (!lineIndexCache.has(sourceCode)) { - const lineIndices = (sourceCode.text.match(/[^\r\n\u2028\u2029]*(\r\n|\r|\n|\u2028|\u2029)/g) || []) - .reduce((indices, line) => indices.concat(indices[indices.length - 1] + line.length), [0]); + const lineIndices = [0]; + const lineEndingPattern = /\r\n|[\r\n\u2028\u2029]/g; + let match; + + /* + * Previously, this function was implemented using a regex that + * matched a sequence of non-linebreak characters followed by a + * linebreak, then adding the lengths of the matches. However, + * this caused a catastrophic backtracking issue when the end + * of a file contained a large number of non-newline characters. + * To avoid this, the current implementation just matches newlines + * and uses match.index to get the correct line start indices. + */ + + while ((match = lineEndingPattern.exec(sourceCode.text))) { + lineIndices.push(match.index + match[0].length); + } // Store the sourceCode object in a WeakMap to avoid iterating over all of the lines every time a sourceCode object is passed in. lineIndexCache.set(sourceCode, lineIndices); diff --git a/tests/lib/rules/no-multiple-empty-lines.js b/tests/lib/rules/no-multiple-empty-lines.js index fe14f7570be..65379321a8f 100644 --- a/tests/lib/rules/no-multiple-empty-lines.js +++ b/tests/lib/rules/no-multiple-empty-lines.js @@ -309,6 +309,13 @@ ruleTester.run("no-multiple-empty-lines", rule, { errors: [getExpectedError(1)], options: [{ max: 1 }], parserOptions: { ecmaVersion: 6 } + }, + { + + // https://github.com/eslint/eslint/issues/7893 + code: `a\n\n\n\n${"a".repeat(1e5)}`, + output: `a\n\n\n${"a".repeat(1e5)}`, + errors: [getExpectedError(2)] } ] });