From f2d1fcdc10130cd107fa38508c37b50136870eeb Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Sun, 28 Apr 2024 09:49:01 -0400 Subject: [PATCH 1/2] fix: OS-agnostic handling of end-of-line characters --- lib/config-list.ts | 3 ++- lib/generator.ts | 11 +++++++---- lib/markdown.ts | 16 ++++++++++------ lib/rule-doc-notices.ts | 3 ++- lib/rule-list-legend.ts | 3 ++- lib/rule-list.ts | 7 ++++--- lib/rule-options-list.ts | 3 ++- lib/string.ts | 4 +++- 8 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/config-list.ts b/lib/config-list.ts index 23a7ad48..bf8cabca 100644 --- a/lib/config-list.ts +++ b/lib/config-list.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { BEGIN_CONFIG_LIST_MARKER, END_CONFIG_LIST_MARKER, @@ -111,5 +112,5 @@ export function updateConfigsList( ignoreConfig ); - return `${preList}${BEGIN_CONFIG_LIST_MARKER}\n\n${list}\n\n${END_CONFIG_LIST_MARKER}${postList}`; + return `${preList}${BEGIN_CONFIG_LIST_MARKER}${EOL}${EOL}${list}${EOL}${EOL}${END_CONFIG_LIST_MARKER}${postList}`; } diff --git a/lib/generator.ts b/lib/generator.ts index 1d633c55..75c92150 100644 --- a/lib/generator.ts +++ b/lib/generator.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join, relative, resolve } from 'node:path'; import { getAllNamedOptions, hasOptions } from './rule-options.js'; @@ -168,16 +169,18 @@ export async function generate(path: string, options?: GenerateOptions) { // The rule doc header will be added later. let newRuleDocContents = [ ruleDocSectionInclude.length > 0 - ? ruleDocSectionInclude.map((title) => `## ${title}`).join('\n\n') + ? ruleDocSectionInclude + .map((title) => `## ${title}`) + .join(`${EOL}${EOL}`) : undefined, ruleHasOptions - ? `## Options\n\n${BEGIN_RULE_OPTIONS_LIST_MARKER}\n${END_RULE_OPTIONS_LIST_MARKER}` + ? `## Options${EOL}${EOL}${BEGIN_RULE_OPTIONS_LIST_MARKER}${EOL}${END_RULE_OPTIONS_LIST_MARKER}` : undefined, ] .filter((section) => section !== undefined) - .join('\n\n'); + .join(`${EOL}${EOL}`); if (newRuleDocContents !== '') { - newRuleDocContents = `\n${newRuleDocContents}\n`; + newRuleDocContents = `${EOL}${newRuleDocContents}${EOL}`; } mkdirSync(dirname(pathToDoc), { recursive: true }); diff --git a/lib/markdown.ts b/lib/markdown.ts index 3496a15d..2a72cbac 100644 --- a/lib/markdown.ts +++ b/lib/markdown.ts @@ -1,3 +1,5 @@ +import { EOL } from 'node:os'; + // General helpers for dealing with markdown files / content. /** @@ -12,7 +14,7 @@ export function replaceOrCreateHeader( newHeader: string, marker: string ) { - const lines = markdown.split('\n'); + const lines = markdown.split(EOL); const titleLineIndex = lines.findIndex((line) => line.startsWith('# ')); const markerLineIndex = lines.indexOf(marker); @@ -22,16 +24,18 @@ export function replaceOrCreateHeader( // Any YAML front matter or anything else above the title should be kept as-is ahead of the new header. const preHeader = lines .slice(0, Math.max(titleLineIndex, dashesLineIndex2 + 1)) - .join('\n'); + .join(EOL); // Anything after the marker comment, title, or YAML front matter should be kept as-is after the new header. const postHeader = lines .slice( Math.max(markerLineIndex + 1, titleLineIndex + 1, dashesLineIndex2 + 1) ) - .join('\n'); + .join(EOL); - return `${preHeader ? `${preHeader}\n` : ''}${newHeader}\n${postHeader}`; + return `${ + preHeader ? `${preHeader}${EOL}` : '' + }${newHeader}${EOL}${postHeader}`; } /** @@ -42,7 +46,7 @@ export function findSectionHeader( str: string ): string | undefined { // Get all the matching strings. - const regexp = new RegExp(`## .*${str}.*\n`, 'giu'); + const regexp = new RegExp(`## .*${str}.*${EOL}`, 'giu'); const sectionPotentialMatches = [...markdown.matchAll(regexp)].map( (match) => match[0] ); @@ -64,7 +68,7 @@ export function findSectionHeader( } export function findFinalHeaderLevel(str: string) { - const lines = str.split('\n'); + const lines = str.split(EOL); const finalHeader = lines.reverse().find((line) => line.match('^(#+) .+$')); return finalHeader ? finalHeader.indexOf(' ') : undefined; } diff --git a/lib/rule-doc-notices.ts b/lib/rule-doc-notices.ts index b412d9e9..fabf9626 100644 --- a/lib/rule-doc-notices.ts +++ b/lib/rule-doc-notices.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { END_RULE_HEADER_MARKER } from './comment-markers.js'; import { EMOJI_DEPRECATED, @@ -523,5 +524,5 @@ export function generateRuleHeaderLines( ), '', END_RULE_HEADER_MARKER, - ].join('\n'); + ].join(EOL); } diff --git a/lib/rule-list-legend.ts b/lib/rule-list-legend.ts index 18619f96..6b11efa9 100644 --- a/lib/rule-list-legend.ts +++ b/lib/rule-list-legend.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { EMOJI_DEPRECATED, EMOJI_FIXABLE, @@ -287,5 +288,5 @@ export function generateLegend( ); } - return legends.join('\\\n'); // Back slash ensures these end up displayed on separate lines. + return legends.join(`\\${EOL}`); // Back slash ensures these end up displayed on separate lines. } diff --git a/lib/rule-list.ts b/lib/rule-list.ts index 9b5820e6..5f9dccc4 100644 --- a/lib/rule-list.ts +++ b/lib/rule-list.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { BEGIN_RULE_LIST_MARKER, END_RULE_LIST_MARKER, @@ -288,7 +289,7 @@ function generateRuleListMarkdownForRulesAndHeaders( ); } - return parts.join('\n\n'); + return parts.join(`${EOL}${EOL}`); } /** @@ -546,7 +547,7 @@ export function updateRulesList( urlRuleDoc ); - const newContent = `${legend ? `${legend}\n\n` : ''}${list}`; + const newContent = `${legend ? `${legend}${EOL}${EOL}` : ''}${list}`; - return `${preList}${BEGIN_RULE_LIST_MARKER}\n\n${newContent}\n\n${END_RULE_LIST_MARKER}${postList}`; + return `${preList}${BEGIN_RULE_LIST_MARKER}${EOL}${EOL}${newContent}${EOL}${EOL}${END_RULE_LIST_MARKER}${postList}`; } diff --git a/lib/rule-options-list.ts b/lib/rule-options-list.ts index eee4acfb..4339f6cd 100644 --- a/lib/rule-options-list.ts +++ b/lib/rule-options-list.ts @@ -1,3 +1,4 @@ +import { EOL } from 'node:os'; import { BEGIN_RULE_OPTIONS_LIST_MARKER, END_RULE_OPTIONS_LIST_MARKER, @@ -159,5 +160,5 @@ export function updateRuleOptionsList( // New rule options list. const list = generateRuleOptionsListMarkdown(rule); - return `${preList}${BEGIN_RULE_OPTIONS_LIST_MARKER}\n\n${list}\n\n${END_RULE_OPTIONS_LIST_MARKER}${postList}`; + return `${preList}${BEGIN_RULE_OPTIONS_LIST_MARKER}${EOL}${EOL}${list}${EOL}${EOL}${END_RULE_OPTIONS_LIST_MARKER}${postList}`; } diff --git a/lib/string.ts b/lib/string.ts index 0fada49d..6ae666e0 100644 --- a/lib/string.ts +++ b/lib/string.ts @@ -1,3 +1,5 @@ +import { EOL } from 'node:os'; + export function toSentenceCase(str: string) { return str.replace(/^\w/u, function (txt) { return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase(); @@ -20,7 +22,7 @@ export function capitalizeOnlyFirstLetter(str: string) { } function sanitizeMarkdownTableCell(text: string): string { - return text.replace(/\|/gu, '\\|').replace(/\n/gu, '
'); + return text.replace(/\|/gu, '\\|').replace(new RegExp(EOL, 'gu'), '
'); } export function sanitizeMarkdownTable( From f392f91e0fb6450e6f42f3cbfa45513d0e7afb9a Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:38:27 -0400 Subject: [PATCH 2/2] try stubbing EOL in test --- jest.config.cjs | 1 + test/jest.setup.cjs | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 test/jest.setup.cjs diff --git a/jest.config.cjs b/jest.config.cjs index f0111b92..34937dd6 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -6,6 +6,7 @@ const jestConfig = { preset: 'ts-jest/presets/default-esm', testEnvironment: 'node', testMatch: ['/test/**/*-test.ts'], + setupFiles: ['/test/jest.setup.cjs'], transform: { '^.+\\.tsx?$': ['ts-jest', { useESM: true }], }, diff --git a/test/jest.setup.cjs b/test/jest.setup.cjs new file mode 100644 index 00000000..82c48591 --- /dev/null +++ b/test/jest.setup.cjs @@ -0,0 +1,6 @@ +const os = require('node:os'); +const sinon = require('sinon'); + +module.exports = function () { + sinon.stub(os, 'EOL').value('\n'); // Stub os.EOL to always be '\n' for testing/snapshot purposes. +};