Promote applyFix and applyFixes helpers into core library.

This commit is contained in:
David Anson 2024-10-06 17:24:44 -07:00
parent e0219411c6
commit 4e30462216
13 changed files with 898 additions and 823 deletions

View file

@ -4,7 +4,6 @@
// Dependencies
var markdownit = globalThis.markdownit;
var markdownlint = globalThis.markdownlint.library;
var helpers = globalThis.markdownlint.helpers;
var micromark = globalThis.micromarkBrowser;
var micromarkHtml = globalThis.micromarkHtmlBrowser;
@ -184,7 +183,7 @@
var errors = e.shiftKey ?
allLintErrors :
[ JSON.parse(decodeURIComponent(e.target.target)) ];
var fixed = helpers.applyFixes(markdown.value, errors);
var fixed = markdownlint.applyFixes(markdown.value, errors);
markdown.value = fixed;
onMarkdownInput();
e.preventDefault();

View file

@ -22,17 +22,7 @@ module.exports.newLineRe = newLineRe;
module.exports.nextLinesRe = nextLinesRe;
/** @typedef {import("../lib/markdownlint.js").RuleOnError} RuleOnError */
/** @typedef {import("../lib/markdownlint.js").RuleOnErrorInfo} RuleOnErrorInfo */
/** @typedef {import("../lib/markdownlint.js").RuleOnErrorFixInfo} RuleOnErrorFixInfo */
/**
* RuleOnErrorInfo with common optional properties filled in.
*
* @typedef {Object} RuleOnErrorFixInfoNormalized
* @property {number} lineNumber Line number (1-based).
* @property {number} editColumn Column of the fix (1-based).
* @property {number} deleteCount Count of characters to delete.
* @property {string} insertText Text to insert (after deleting).
*/
// Regular expression for matching common front matter (YAML and TOML)
module.exports.frontMatterRe =
@ -566,123 +556,6 @@ function getPreferredLineEnding(input, os) {
}
module.exports.getPreferredLineEnding = getPreferredLineEnding;
/**
* Normalizes the fields of a RuleOnErrorFixInfo instance.
*
* @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
* @param {number} [lineNumber] Line number.
* @returns {RuleOnErrorFixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
*/
function normalizeFixInfo(fixInfo, lineNumber = 0) {
return {
"lineNumber": fixInfo.lineNumber || lineNumber,
"editColumn": fixInfo.editColumn || 1,
"deleteCount": fixInfo.deleteCount || 0,
"insertText": fixInfo.insertText || ""
};
}
/**
* Fixes the specified error on a line of Markdown content.
*
* @param {string} line Line of Markdown content.
* @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
* @param {string} [lineEnding] Line ending to use.
* @returns {string | null} Fixed content.
*/
function applyFix(line, fixInfo, lineEnding) {
const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo);
const editIndex = editColumn - 1;
return (deleteCount === -1) ?
null :
line.slice(0, editIndex) +
insertText.replace(/\n/g, lineEnding || "\n") +
line.slice(editIndex + deleteCount);
}
module.exports.applyFix = applyFix;
/**
* Applies as many fixes as possible to Markdown content.
*
* @param {string} input Lines of Markdown content.
* @param {RuleOnErrorInfo[]} errors RuleOnErrorInfo instances.
* @returns {string} Corrected content.
*/
function applyFixes(input, errors) {
const lineEnding = getPreferredLineEnding(input, __webpack_require__(/*! node:os */ "?0176"));
const lines = input.split(newLineRe);
// Normalize fixInfo objects
let fixInfos = errors
.filter((error) => error.fixInfo)
// @ts-ignore
.map((error) => normalizeFixInfo(error.fixInfo, error.lineNumber));
// Sort bottom-to-top, line-deletes last, right-to-left, long-to-short
fixInfos.sort((a, b) => {
const aDeletingLine = (a.deleteCount === -1);
const bDeletingLine = (b.deleteCount === -1);
return (
(b.lineNumber - a.lineNumber) ||
(aDeletingLine ? 1 : (bDeletingLine ? -1 : 0)) ||
(b.editColumn - a.editColumn) ||
(b.insertText.length - a.insertText.length)
);
});
// Remove duplicate entries (needed for following collapse step)
// eslint-disable-next-line jsdoc/valid-types
/** @type RuleOnErrorFixInfo */
let lastFixInfo = {};
fixInfos = fixInfos.filter((fixInfo) => {
const unique = (
(fixInfo.lineNumber !== lastFixInfo.lineNumber) ||
(fixInfo.editColumn !== lastFixInfo.editColumn) ||
(fixInfo.deleteCount !== lastFixInfo.deleteCount) ||
(fixInfo.insertText !== lastFixInfo.insertText)
);
lastFixInfo = fixInfo;
return unique;
});
// Collapse insert/no-delete and no-insert/delete for same line/column
lastFixInfo = {
"lineNumber": -1
};
for (const fixInfo of fixInfos) {
if (
(fixInfo.lineNumber === lastFixInfo.lineNumber) &&
(fixInfo.editColumn === lastFixInfo.editColumn) &&
!fixInfo.insertText &&
(fixInfo.deleteCount > 0) &&
lastFixInfo.insertText &&
!lastFixInfo.deleteCount) {
fixInfo.insertText = lastFixInfo.insertText;
lastFixInfo.lineNumber = 0;
}
lastFixInfo = fixInfo;
}
fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber);
// Apply all (remaining/updated) fixes
let lastLineIndex = -1;
let lastEditIndex = -1;
for (const fixInfo of fixInfos) {
const { lineNumber, editColumn, deleteCount } = fixInfo;
const lineIndex = lineNumber - 1;
const editIndex = editColumn - 1;
if (
(lineIndex !== lastLineIndex) ||
(deleteCount === -1) ||
((editIndex + deleteCount) <=
(lastEditIndex - ((deleteCount > 0) ? 0 : 1)))
) {
// @ts-ignore
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
}
lastLineIndex = lineIndex;
lastEditIndex = editIndex;
}
// Return corrected input
return lines.filter((line) => line !== null).join(lineEnding);
}
module.exports.applyFixes = applyFixes;
/**
* Expands a path with a tilde to an absolute path.
*
@ -766,16 +639,6 @@ module.exports = micromarkBrowser;
/***/ }),
/***/ "?0176":
/*!*************************!*\
!*** node:os (ignored) ***!
\*************************/
/***/ (() => {
/* (ignored) */
/***/ }),
/***/ "?d0ee":
/*!*************************!*\
!*** node:fs (ignored) ***!
@ -2849,6 +2712,119 @@ function readConfigSync(file, parsers, fs) {
return config;
}
/**
* Normalizes the fields of a RuleOnErrorFixInfo instance.
*
* @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
* @param {number} [lineNumber] Line number.
* @returns {RuleOnErrorFixInfoNormalized} Normalized RuleOnErrorFixInfo instance.
*/
function normalizeFixInfo(fixInfo, lineNumber = 0) {
return {
"lineNumber": fixInfo.lineNumber || lineNumber,
"editColumn": fixInfo.editColumn || 1,
"deleteCount": fixInfo.deleteCount || 0,
"insertText": fixInfo.insertText || ""
};
}
/**
* Applies the specified fix to a Markdown content line.
*
* @param {string} line Line of Markdown content.
* @param {RuleOnErrorFixInfo} fixInfo RuleOnErrorFixInfo instance.
* @param {string} [lineEnding] Line ending to use.
* @returns {string | null} Fixed content or null if deleted.
*/
function applyFix(line, fixInfo, lineEnding = "\n") {
const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo);
const editIndex = editColumn - 1;
return (deleteCount === -1) ?
null :
line.slice(0, editIndex) + insertText.replace(/\n/g, lineEnding) + line.slice(editIndex + deleteCount);
}
/**
* Applies as many of the specified fixes as possible to Markdown content.
*
* @param {string} input Lines of Markdown content.
* @param {RuleOnErrorInfo[]} errors RuleOnErrorInfo instances.
* @returns {string} Fixed content.
*/
function applyFixes(input, errors) {
const lineEnding = helpers.getPreferredLineEnding(input, __webpack_require__(/*! node:os */ "?e6c4"));
const lines = input.split(helpers.newLineRe);
// Normalize fixInfo objects
let fixInfos = errors
.filter((error) => error.fixInfo)
// @ts-ignore
.map((error) => normalizeFixInfo(error.fixInfo, error.lineNumber));
// Sort bottom-to-top, line-deletes last, right-to-left, long-to-short
fixInfos.sort((a, b) => {
const aDeletingLine = (a.deleteCount === -1);
const bDeletingLine = (b.deleteCount === -1);
return (
(b.lineNumber - a.lineNumber) ||
(aDeletingLine ? 1 : (bDeletingLine ? -1 : 0)) ||
(b.editColumn - a.editColumn) ||
(b.insertText.length - a.insertText.length)
);
});
// Remove duplicate entries (needed for following collapse step)
// eslint-disable-next-line jsdoc/valid-types
/** @type RuleOnErrorFixInfo */
let lastFixInfo = {};
fixInfos = fixInfos.filter((fixInfo) => {
const unique = (
(fixInfo.lineNumber !== lastFixInfo.lineNumber) ||
(fixInfo.editColumn !== lastFixInfo.editColumn) ||
(fixInfo.deleteCount !== lastFixInfo.deleteCount) ||
(fixInfo.insertText !== lastFixInfo.insertText)
);
lastFixInfo = fixInfo;
return unique;
});
// Collapse insert/no-delete and no-insert/delete for same line/column
lastFixInfo = {
"lineNumber": -1
};
for (const fixInfo of fixInfos) {
if (
(fixInfo.lineNumber === lastFixInfo.lineNumber) &&
(fixInfo.editColumn === lastFixInfo.editColumn) &&
!fixInfo.insertText &&
(fixInfo.deleteCount > 0) &&
lastFixInfo.insertText &&
!lastFixInfo.deleteCount) {
fixInfo.insertText = lastFixInfo.insertText;
lastFixInfo.lineNumber = 0;
}
lastFixInfo = fixInfo;
}
fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber);
// Apply all (remaining/updated) fixes
let lastLineIndex = -1;
let lastEditIndex = -1;
for (const fixInfo of fixInfos) {
const { lineNumber, editColumn, deleteCount } = fixInfo;
const lineIndex = lineNumber - 1;
const editIndex = editColumn - 1;
if (
(lineIndex !== lastLineIndex) ||
(deleteCount === -1) ||
((editIndex + deleteCount) <=
(lastEditIndex - ((deleteCount > 0) ? 0 : 1)))
) {
// @ts-ignore
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
}
lastLineIndex = lineIndex;
lastEditIndex = editIndex;
}
// Return corrected input
return lines.filter((line) => line !== null).join(lineEnding);
}
/**
* Gets the (semantic) version of the library.
*
@ -2868,6 +2844,8 @@ markdownlint.promises = {
"extendConfig": extendConfigPromise,
"readConfig": readConfigPromise
};
markdownlint.applyFix = applyFix;
markdownlint.applyFixes = applyFixes;
module.exports = markdownlint;
// Type declarations
@ -2986,6 +2964,16 @@ module.exports = markdownlint;
* @property {string} [insertText] Text to insert (after deleting).
*/
/**
* RuleOnErrorInfo with all optional properties present.
*
* @typedef {Object} RuleOnErrorFixInfoNormalized
* @property {number} lineNumber Line number (1-based).
* @property {number} editColumn Column of the fix (1-based).
* @property {number} deleteCount Count of characters to delete.
* @property {string} insertText Text to insert (after deleting).
*/
/**
* Rule definition.
*