UNPKG

eslint-doc-generator

Version:

Automatic documentation generator for ESLint plugins and rules.

81 lines (80 loc) 2.94 kB
import { EOL } from 'node:os'; import { resolve } from 'node:path'; import editorconfig from 'editorconfig'; /** * Create a memoized end-of-line resolver scoped to one `generate()` run. * Cache keys are absolute file paths so sibling `.md`/`.mdx` files can differ. * * Write-time precedence for `resolve()`: * 1. Explicit EditorConfig `end_of_line` * 2. Predominant end of line in existing contents (skip when `contents` is undefined) * 3. `os.EOL` * * Prettier config is not read — run Prettier via `postprocess` if needed. */ export function createEndOfLineResolver() { const explicitCache = new Map(); /** Shared across files so EditorConfig does not re-read the same files. */ const editorConfigFileCache = new Map(); async function getExplicitEndOfLine(filePath) { const absolutePath = resolve(filePath); let cached = explicitCache.get(absolutePath); if (!cached) { cached = getEndOfLineFromEditorConfig(absolutePath, editorConfigFileCache); explicitCache.set(absolutePath, cached); } const result = await cached; return result; } async function resolveFileEndOfLine(filePath, contents) { return ((await getExplicitEndOfLine(filePath)) ?? (contents === undefined ? undefined : detectEndOfLine(contents)) ?? getFallbackEndOfLine()); } return { getExplicitEndOfLine, resolve: resolveFileEndOfLine, }; } async function getEndOfLineFromEditorConfig(filePath, cache) { const editorConfigProps = await editorconfig.parse(filePath, { cache }); if (editorConfigProps.end_of_line === 'lf') { return '\n'; } if (editorConfigProps.end_of_line === 'crlf') { return '\r\n'; } return undefined; } /** * Detect the predominant end of line in the given file contents. * Returns undefined if the contents have no line breaks. */ export function detectEndOfLine(contents) { const crlfCount = contents.split('\r\n').length - 1; // All LFs minus those that are part of a CRLF. const lfCount = contents.split('\n').length - 1 - crlfCount; if (crlfCount === 0 && lfCount === 0) { return undefined; } // A tie favors LF. return crlfCount > lfCount ? '\r\n' : '\n'; } /** * Convert all line endings in the given contents to the given end of line. */ export function normalizeEndOfLine(contents, endOfLine) { return contents.replaceAll(/\r\n|[\r\n]/gu, endOfLine); } /** Fallback when there is no config and no detectable end of line. */ export function getFallbackEndOfLine() { return getNodeEOL(); } /* istanbul ignore next */ /** `EOL` is typed as `string`, so we perform run-time validation to be safe. */ function getNodeEOL() { if (EOL === '\n' || EOL === '\r\n') { return EOL; } throw new Error(`Failed to detect the end-of-line constant from the JavaScript runtime: ${EOL}`); }