UNPKG

eslint-seatbelt

Version:

Gradually tighten ESLint rules in your codebase

328 lines (324 loc) 9.98 kB
import { SEATBELT_FROZEN, SEATBELT_KEEP, SeatbeltArgs, formatFilename, formatRuleId, name } from "./chunk-FSBX2ZEB.mjs"; // src/SeatbeltFile.ts import * as os from "node:os"; import * as fs from "node:fs"; import path, * as nodePath from "node:path"; // src/errorHanding.ts function appendErrorContext(error, context) { if (error instanceof Error) { error.message += ` ${context}`; } } function isErrno(error, code) { return error instanceof Error && "code" in error && error.code === code; } // src/SeatbeltFile.ts function encodeLine(line) { const { filename, ruleId, maxErrors } = line; return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} `; } function decodeLine(line, index) { try { const lineParts = line.split(" "); if (lineParts.length !== 3) { throw new Error( `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` ); } let filename; try { filename = JSON.parse(lineParts[0]); } catch (e) { appendErrorContext(e, "at tab-separated column 1 (filename)"); throw e; } let ruleId; try { ruleId = JSON.parse(lineParts[1]); } catch (e) { appendErrorContext(e, "at tab-separated column 2 (RuleId)"); throw e; } let maxErrors; try { maxErrors = JSON.parse(lineParts[2]); } catch (e) { appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); throw e; } return { encoded: line, filename, ruleId, maxErrors }; } catch (e) { appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); throw e; } } var COMMENT_LINE_REGEX = /^\s*#/; var DEFAULT_FILE_HEADER = ` # ${name} temporarily allowed errors # docs: https://github.com/justjake/${name}#readme `.trim(); var SeatbeltFile = class _SeatbeltFile { constructor(filename, data, comments = "") { this.filename = filename; this.data = data; this.comments = comments; this.filename = path.resolve(this.filename); this.dirname = path.dirname(this.filename); } static readSync(filename) { const text = fs.readFileSync(filename, "utf8"); try { return _SeatbeltFile.parse(filename, text); } catch (e) { appendErrorContext(e, `in seatbelt file \`${filename}\``); throw e; } } /** * Read `filename` if it exists, otherwise create a new empty seatbelt file object * that will write to that filename. */ static openSync(filename) { try { return _SeatbeltFile.readSync(filename); } catch (e) { if (isErrno(e, "ENOENT")) { return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); } throw e; } } static parse(filename, text) { const data = /* @__PURE__ */ new Map(); const split = text.split(/(?<=\n)/); const lines = split.filter( (line) => line !== "" && line !== "\n" && !COMMENT_LINE_REGEX.test(line) ).map(decodeLine); const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); lines.forEach((line) => { let fileState = data.get(line.filename); if (!fileState) { fileState = { maxErrors: void 0, lines: [] }; data.set(line.filename, fileState); } fileState.lines.push(line); }); return new _SeatbeltFile(filename, data, comments.trim()); } static fromJSON(json) { const data = new Map( Object.entries(json.data).map(([filename, maxErrors]) => [ filename, { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } ]) ); return new _SeatbeltFile(json.filename, data); } changed = false; dirname; useTempDirForWrites = true; *filenames() { for (const filename of this.data.keys()) { yield this.toAbsolutePath(filename); } } getMaxErrors(filename) { const fileState = this.data.get(this.toRelativePath(filename)); if (!fileState) { return void 0; } fileState.maxErrors ??= parseMaxErrors(fileState.lines); return fileState.maxErrors; } removeFile(filename, args) { const relativeFilename = this.toRelativePath(filename); if (!this.data.has(relativeFilename)) { return false; } SeatbeltArgs.verboseLog( args, () => args.frozen ? `${formatFilename(filename)}: ${SEATBELT_FROZEN}: didn't remove max errors` : `${formatFilename(filename)}: remove max errors` ); if (args.frozen) { return false; } this.data.delete(relativeFilename); this.changed = true; return true; } updateMaxErrors(filename, args, ruleToErrorCount) { const removedRules = /* @__PURE__ */ new Set(); let increasedRulesCount = 0; let decreasedRulesCount = 0; this.getMaxErrors(filename); const relativeFilename = this.toRelativePath(filename); const maxErrors = this.data.get(relativeFilename)?.maxErrors ?? /* @__PURE__ */ new Map(); ruleToErrorCount.forEach((errorCount, ruleId) => { const maxErrorCount = maxErrors.get(ruleId) ?? 0; if (errorCount === maxErrorCount) { return; } if (errorCount < maxErrorCount || SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { SeatbeltArgs.verboseLog( args, () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` ); maxErrors.set(ruleId, errorCount); if (errorCount > maxErrorCount) { increasedRulesCount++; } else { decreasedRulesCount++; } } }); if (args.verbose || args.keepRules !== "all") { maxErrors.forEach((maxErrorCount, ruleId) => { const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); if (!shouldRemove) { return; } if (SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { SeatbeltArgs.verboseLog( args, () => `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` ); return; } SeatbeltArgs.verboseLog( args, () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${0}` ); maxErrors.delete(ruleId); removedRules.add(ruleId); }); } const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; if (changed && !args.frozen) { const file = this.data.get(relativeFilename); if (file) { file.maxErrors = maxErrors; } else { this.data.set(relativeFilename, { maxErrors, lines: [] }); } this.changed = true; } return { removedRules, increasedRulesCount, decreasedRulesCount }; } toDataString() { const lines = []; this.data.forEach((fileState, filename) => { if (fileState.maxErrors) { fileState.lines = []; fileState.maxErrors.forEach((maxErrorCount, ruleId) => { fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); }); fileState.lines.sort( (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 ); } fileState.lines.forEach((line) => { const encoded = line.encoded ??= encodeLine(line); lines.push(encoded); }); }); lines.sort(); if (this.comments) { return this.comments + "\n\n" + lines.join(""); } else { return lines.join(""); } } readSync() { const nextStateFile = _SeatbeltFile.openSync(this.filename); if (nextStateFile) { this.data = nextStateFile.data; this.changed = false; return true; } return false; } flushChanges() { if (this.changed) { this.writeSync(); this.changed = false; return { updated: true }; } return { updated: false }; } writeSync() { const dataString = this.toDataString(); const dir = nodePath.dirname(this.filename); const base = nodePath.basename(this.filename); const tempFile = nodePath.join( this.useTempDirForWrites ? os.tmpdir() : dir, `.${base}.wip${process.pid}.${Date.now()}.tmp` ); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(tempFile, dataString, "utf8"); try { fs.renameSync(tempFile, this.filename); } catch (error) { if (isErrno(error, "EXDEV")) { this.useTempDirForWrites = false; fs.copyFileSync(tempFile, this.filename); fs.rmSync(tempFile); return; } throw error; } } toJSON() { const data = Object.fromEntries( Array.from(this.data.keys()).map((filename) => { const maxErrors = this.getMaxErrors(filename); if (!maxErrors) { throw new Error(`${name} bug: expected errors for existing key`); } return [filename, Object.fromEntries(maxErrors)]; }) ); return { filename: this.filename, data }; } toRelativePath(filename) { if (!nodePath.isAbsolute(filename)) { return filename; } return nodePath.relative(this.dirname, filename); } toAbsolutePath(filename) { if (nodePath.isAbsolute(filename)) { return filename; } return nodePath.resolve(this.dirname, filename); } }; function parseMaxErrors(lines) { const maxErrors = /* @__PURE__ */ new Map(); lines.forEach((line) => { maxErrors.set(line.ruleId, line.maxErrors); }); return maxErrors; } export { appendErrorContext, isErrno, SeatbeltFile }; //# sourceMappingURL=chunk-YUN52PM5.mjs.map