diff --git a/lib/rules/template-no-multiple-empty-lines.js b/lib/rules/template-no-multiple-empty-lines.js index edb6e80932..ea5b4ea12f 100644 --- a/lib/rules/template-no-multiple-empty-lines.js +++ b/lib/rules/template-no-multiple-empty-lines.js @@ -49,10 +49,40 @@ module.exports = { offset += line.length + 1; // +1 for the '\n' } + // Swallow the final newline, as some editors add it automatically + // and we don't want it to cause an issue. + const effectiveLines = lines.length > 0 && lines.at(-1) === '' ? lines.slice(0, -1) : lines; + let emptyCount = 0; let firstEmptyLine = -1; - for (const [index, line] of lines.entries()) { + function reportExcess(endIndex) { + const startLine = firstEmptyLine + max; + const endLine = endIndex; + + // Remove the excess empty lines: keep `max` empty lines, + // remove everything from the start of the (max+1)-th empty + // line to the start of the next non-empty line (or end of content). + const rangeStart = lineOffsets[firstEmptyLine + max]; + const rangeEnd = endIndex < lines.length ? lineOffsets[endIndex] : text.length; + + context.report({ + loc: { + start: { line: startLine + 1, column: 0 }, + end: { line: endLine + 1, column: 0 }, + }, + messageId: 'unexpected', + data: { + max, + pluralizedLines: max === 1 ? 'line' : 'lines', + }, + fix(fixer) { + return fixer.replaceTextRange([rangeStart, rangeEnd], ''); + }, + }); + } + + for (const [index, line] of effectiveLines.entries()) { if (line.trim() === '') { if (emptyCount === 0) { firstEmptyLine = index; @@ -60,34 +90,17 @@ module.exports = { emptyCount++; } else { if (emptyCount > max) { - const startLine = firstEmptyLine + max + 1; - const endLine = index; - - // Remove the excess empty lines: keep `max` empty lines, - // remove everything from the start of the (max+1)-th empty - // line to the start of the next non-empty line. - const rangeStart = lineOffsets[firstEmptyLine + max]; - const rangeEnd = lineOffsets[endLine]; - - context.report({ - loc: { - start: { line: startLine + 1, column: 0 }, - end: { line: endLine, column: 0 }, - }, - messageId: 'unexpected', - data: { - max, - pluralizedLines: max === 1 ? 'line' : 'lines', - }, - fix(fixer) { - return fixer.replaceTextRange([rangeStart, rangeEnd], ''); - }, - }); + reportExcess(index); } emptyCount = 0; firstEmptyLine = -1; } } + + // Handle trailing empty lines at the end of the effective content + if (emptyCount > max) { + reportExcess(effectiveLines.length); + } }, }; }, diff --git a/tests/lib/rules/template-no-multiple-empty-lines.js b/tests/lib/rules/template-no-multiple-empty-lines.js index 098f3b634b..c7f6e94cfe 100644 --- a/tests/lib/rules/template-no-multiple-empty-lines.js +++ b/tests/lib/rules/template-no-multiple-empty-lines.js @@ -177,5 +177,22 @@ hbsRuleTester.run('template-no-multiple-empty-lines', rule, { options: [{ max: 3 }], errors: [{ message: 'More than 3 blank lines not allowed.' }], }, + // loc fix: the excess empty line (line 3) is reported at the correct location + { + code: '
foo
\n\n\n
bar
', + output: '
foo
\n\n
bar
', + errors: [{ message: 'More than 1 blank line not allowed.', line: 3, endLine: 4 }], + }, + // Trailing empty lines + { + code: '
foo
\n\n\n', + output: '
foo
\n\n', + errors: [{ message: 'More than 1 blank line not allowed.' }], + }, + { + code: '
foo
\n\n\n\n\n', + output: '
foo
\n\n', + errors: [{ message: 'More than 1 blank line not allowed.' }], + }, ], });