diff --git a/scripts/linters/lint-changelog.ts b/scripts/linters/lint-changelog.ts index aff1abe51..4acaf4741 100644 --- a/scripts/linters/lint-changelog.ts +++ b/scripts/linters/lint-changelog.ts @@ -24,8 +24,10 @@ * Lists * - Items start with "* " (not "- ") * - No trailing whitespace + * - No blank lines between consecutive list items * * Spacing + * - No leading blank lines before the title * - Exactly 1 blank line before every ## heading (not before the first one) * - Exactly 1 blank line after every # and ## headings * - No blank lines after ### headings @@ -58,6 +60,33 @@ const collapseBlankLines = (lines: string[]): string[] => { return out } +/** Remove blank lines before the first content line. */ +const stripLeadingBlanks = (lines: string[]): string[] => { + let i = 0 + while (i < lines.length && '' === lines[i].trim()) {i += 1} + return lines.slice(i) +} + +/** + * Drop blank lines that sit between two list items. Bullets accumulate stray + * blank lines over successive edits; consecutive items should be contiguous. + */ +const removeBlanksBetweenListItems = (lines: string[]): string[] => { + const isItem = (l: string): boolean => /^\s*[*-] /.test(l) + const out: string[] = [] + for (let i = 0; i < lines.length; i += 1) { + if ('' === lines[i].trim()) { + let j = i + 1 + while (j < lines.length && '' === lines[j].trim()) {j += 1} + const prev = out[out.length - 1] + const next = j < lines.length ? lines[j] : '' + if (0 < out.length && isItem(prev) && isItem(next)) {continue} + } + out.push(lines[i]) + } + return out +} + /** * Ensure exactly `n` blank lines appear immediately before every line matching * `headingRe`. Lines matching `skipAfterRe` suppress spacing for the immediately @@ -257,6 +286,7 @@ export const lintChangelog = (src: string): { fixed: string; errors: string[] } const errors: string[] = [] let lines = src.split('\n') + lines = stripLeadingBlanks(lines) lines = ensureTitle(lines, errors) lines = trimTrailing(lines) lines = promoteBoldChangeTypeLabels(lines) @@ -265,6 +295,7 @@ export const lintChangelog = (src: string): { fixed: string; errors: string[] } lines = normaliseIndentedSubListItems(lines) lines = normaliseTopLevelBulletMarkers(lines) lines = applySpacingRules(lines) + lines = removeBlanksBetweenListItems(lines) lines = finaliseLines(lines) return { fixed: lines.join('\n'), errors } diff --git a/scripts/linters/lint-readme.ts b/scripts/linters/lint-readme.ts index 3f51746c2..9d10fa6a4 100644 --- a/scripts/linters/lint-readme.ts +++ b/scripts/linters/lint-readme.ts @@ -28,8 +28,10 @@ * Lists * - Items start with "* " (not "- ") * - No trailing whitespace + * - No blank lines between consecutive list items * * Spacing + * - No leading blank lines before the plugin header * - Exactly 1 blank line before every == section (not the very first) * - Exactly 1 blank line after every == section heading * - Exactly 1 blank line before every = sub-section (not right after == heading) @@ -98,6 +100,39 @@ const collapseBlankLines = (lines: string[]): string[] => { return out } +/** Remove blank lines before the first content line. */ +const stripLeadingBlanks = (lines: string[]): string[] => { + let i = 0 + while (i < lines.length && '' === lines[i].trim()) { + i += 1 + } + return lines.slice(i) +} + +/** + * Drop blank lines that sit between two list items. Bullets accumulate stray + * blank lines over successive edits; consecutive items should be contiguous. + */ +const removeBlanksBetweenListItems = (lines: string[]): string[] => { + const isItem = (l: string): boolean => /^\s*[*-] /.test(l) + const out: string[] = [] + for (let i = 0; i < lines.length; i += 1) { + if ('' === lines[i].trim()) { + let j = i + 1 + while (j < lines.length && '' === lines[j].trim()) { + j += 1 + } + const prev = out[out.length - 1] + const next = j < lines.length ? lines[j] : '' + if (0 < out.length && isItem(prev) && isItem(next)) { + continue + } + } + out.push(lines[i]) + } + return out +} + /** * Ensure exactly `n` blank lines appear immediately before every line matching * `headingRe`. Lines matching `skipAfterRe` reset the "just-saw-section-start" @@ -345,6 +380,7 @@ export const lintReadme = (src: string): { fixed: string; errors: string[] } => const errors: string[] = [] let lines = src.split('\n') + lines = stripLeadingBlanks(lines) lines = normalisePluginHeader(lines, errors) lines = trimTrailing(lines) lines = normaliseHeaderFieldSpacing(lines) @@ -354,6 +390,7 @@ export const lintReadme = (src: string): { fixed: string; errors: string[] } => lines = normaliseChangelogChangeTypes(lines) lines = normaliseListItems(lines) lines = applySpacingRules(lines) + lines = removeBlanksBetweenListItems(lines) lines = finaliseLines(lines) return { fixed: lines.join('\n'), errors }