mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-22 05:40:48 +02:00
Promote applyFix and applyFixes helpers into core library.
This commit is contained in:
parent
e0219411c6
commit
4e30462216
13 changed files with 898 additions and 823 deletions
46
README.md
46
README.md
|
@ -768,6 +768,44 @@ Type: `Object`
|
|||
|
||||
Configuration object.
|
||||
|
||||
### Fixing
|
||||
|
||||
Rules that can be fixed automatically include a `fixInfo` property which is
|
||||
outlined in the [documentation for custom rules](doc/CustomRules.md#authoring).
|
||||
To apply fixes consistently, the `applyFix`/`applyFixes` methods may be used:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 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") { ... }
|
||||
|
||||
/**
|
||||
* 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) { ... }
|
||||
```
|
||||
|
||||
Invoking `applyFixes` with the results of a call to lint can be done like so:
|
||||
|
||||
```javascript
|
||||
const { "sync": markdownlintSync, applyFixes } = require("markdownlint");
|
||||
|
||||
function fixMarkdownlintViolations(content) {
|
||||
const fixResults = markdownlintSync({ strings: { content } });
|
||||
return applyFixes(content, fixResults.content);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Invoke `markdownlint` and use the `result` object's `toString` method:
|
||||
|
@ -934,14 +972,6 @@ bad.md: 1: MD041/first-line-heading/first-line-h1 First line in a file should be
|
|||
Use --force to continue.
|
||||
```
|
||||
|
||||
### Fixing
|
||||
|
||||
Rules that can be fixed automatically include a `fixInfo` property which is
|
||||
outlined in the [documentation for custom rules](doc/CustomRules.md#authoring).
|
||||
To apply those fixes more easily, the `applyFixes` method in
|
||||
[markdownlint-rule-helpers](helpers/README.md#applying-recommended-fixes) may
|
||||
be used.
|
||||
|
||||
## Browser
|
||||
|
||||
`markdownlint` also works in the browser.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -166,6 +166,36 @@ markdownlint(options, assertLintResultsCallback);
|
|||
assertLintResultsCallback(null, await markdownlint.promises.markdownlint(options));
|
||||
})();
|
||||
|
||||
assert.equal(
|
||||
markdownlint.applyFix(
|
||||
"# Fixing\n",
|
||||
{
|
||||
"insertText": "Head",
|
||||
"editColumn": 3,
|
||||
"deleteCount": 3
|
||||
},
|
||||
"\n"
|
||||
),
|
||||
"# Heading\n"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
markdownlint.applyFixes(
|
||||
"# Fixing\n",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"insertText": "Head",
|
||||
"editColumn": 3,
|
||||
"deleteCount": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
),
|
||||
"# Heading\n"
|
||||
);
|
||||
|
||||
const configuration: markdownlint.Configuration = {
|
||||
"custom-rule": true,
|
||||
"no-hard-tabs": false,
|
||||
|
|
|
@ -17,22 +17,6 @@ change from release to release. There are brief descriptive comments above each
|
|||
function, but no [JSDoc][jsdoc] annotations. That said, some of what's here will
|
||||
be useful to custom rule authors and may avoid duplicating code.
|
||||
|
||||
## Examples
|
||||
|
||||
### Applying Recommended Fixes
|
||||
|
||||
```javascript
|
||||
const { "sync": markdownlintSync } = require("markdownlint");
|
||||
const markdownlintRuleHelpers = require("markdownlint-rule-helpers");
|
||||
|
||||
function fixMarkdownlintViolations(content) {
|
||||
const fixResults = markdownlintSync({ strings: { content } });
|
||||
return markdownlintRuleHelpers.applyFixes(content, fixResults.content);
|
||||
}
|
||||
```
|
||||
|
||||
See also: [`markdownlint` built-in rule implementations][lib].
|
||||
|
||||
## Tests
|
||||
|
||||
*None* - The entire body of code is tested to 100% coverage by the core
|
||||
|
@ -40,7 +24,6 @@ See also: [`markdownlint` built-in rule implementations][lib].
|
|||
|
||||
[custom-rules]: https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/CustomRules.md
|
||||
[jsdoc]: https://en.m.wikipedia.org/wiki/JSDoc
|
||||
[lib]: https://github.com/DavidAnson/markdownlint/tree/v0.35.0/lib
|
||||
[markdown]: https://en.wikipedia.org/wiki/Markdown
|
||||
[markdownlint]: https://github.com/DavidAnson/markdownlint
|
||||
[rules]: https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/Rules.md
|
||||
|
|
|
@ -10,17 +10,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 =
|
||||
|
@ -554,123 +544,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, require("node:os"));
|
||||
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.
|
||||
*
|
||||
|
|
40
lib/markdownlint.d.ts
vendored
40
lib/markdownlint.d.ts
vendored
|
@ -8,7 +8,7 @@ export = markdownlint;
|
|||
*/
|
||||
declare function markdownlint(options: Options | null, callback: LintCallback): void;
|
||||
declare namespace markdownlint {
|
||||
export { markdownlintSync as sync, readConfig, readConfigSync, getVersion, promises, RuleFunction, RuleParams, MarkdownParsers, ParserMarkdownIt, ParserMicromark, MarkdownItToken, MicromarkTokenType, MicromarkToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintContentCallback, LintCallback, Configuration, ConfigurationStrict, RuleConfiguration, ConfigurationParser, ReadConfigCallback, ResolveConfigExtendsCallback };
|
||||
export { markdownlintSync as sync, readConfig, readConfigSync, getVersion, promises, applyFix, applyFixes, RuleFunction, RuleParams, MarkdownParsers, ParserMarkdownIt, ParserMicromark, MarkdownItToken, MicromarkTokenType, MicromarkToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, RuleOnErrorFixInfoNormalized, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintContentCallback, LintCallback, Configuration, ConfigurationStrict, RuleConfiguration, ConfigurationParser, ReadConfigCallback, ResolveConfigExtendsCallback };
|
||||
}
|
||||
/**
|
||||
* Lint specified Markdown files synchronously.
|
||||
|
@ -49,6 +49,23 @@ declare namespace promises {
|
|||
export { extendConfigPromise as extendConfig };
|
||||
export { readConfigPromise as readConfig };
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
declare function applyFix(line: string, fixInfo: RuleOnErrorFixInfo, lineEnding?: string): string | null;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
declare function applyFixes(input: string, errors: RuleOnErrorInfo[]): string;
|
||||
/**
|
||||
* Function to implement rule logic.
|
||||
*/
|
||||
|
@ -270,6 +287,27 @@ type RuleOnErrorFixInfo = {
|
|||
*/
|
||||
insertText?: string;
|
||||
};
|
||||
/**
|
||||
* RuleOnErrorInfo with all optional properties present.
|
||||
*/
|
||||
type RuleOnErrorFixInfoNormalized = {
|
||||
/**
|
||||
* Line number (1-based).
|
||||
*/
|
||||
lineNumber: number;
|
||||
/**
|
||||
* Column of the fix (1-based).
|
||||
*/
|
||||
editColumn: number;
|
||||
/**
|
||||
* Count of characters to delete.
|
||||
*/
|
||||
deleteCount: number;
|
||||
/**
|
||||
* Text to insert (after deleting).
|
||||
*/
|
||||
insertText: string;
|
||||
};
|
||||
/**
|
||||
* Rule definition.
|
||||
*/
|
||||
|
|
|
@ -1204,6 +1204,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, require("node:os"));
|
||||
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.
|
||||
*
|
||||
|
@ -1223,6 +1336,8 @@ markdownlint.promises = {
|
|||
"extendConfig": extendConfigPromise,
|
||||
"readConfig": readConfigPromise
|
||||
};
|
||||
markdownlint.applyFix = applyFix;
|
||||
markdownlint.applyFixes = applyFixes;
|
||||
module.exports = markdownlint;
|
||||
|
||||
// Type declarations
|
||||
|
@ -1341,6 +1456,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.
|
||||
*
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
"lint-test-repos": "ava --timeout=10m test/markdownlint-test-repos-*.js",
|
||||
"serial-config-docs": "npm run build-config && npm run build-docs",
|
||||
"serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration",
|
||||
"test": "ava --timeout=30s test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js helpers/test.cjs",
|
||||
"test": "ava --timeout=30s test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-fixes.js test/markdownlint-test-helpers.js test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js helpers/test.cjs",
|
||||
"test-cover": "c8 --100 npm test",
|
||||
"test-declaration": "cd example/typescript && tsc --module nodenext && tsc --module commonjs && node type-check.js",
|
||||
"test-extra": "ava --timeout=10m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js",
|
||||
|
|
532
test/markdownlint-test-fixes.js
Normal file
532
test/markdownlint-test-fixes.js
Normal file
|
@ -0,0 +1,532 @@
|
|||
// @ts-check
|
||||
|
||||
"use strict";
|
||||
|
||||
const test = require("ava").default;
|
||||
const markdownlint = require("../lib/markdownlint");
|
||||
|
||||
test("applyFix", (t) => {
|
||||
t.plan(4);
|
||||
const testCases = [
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 12,
|
||||
"deleteCount": 1
|
||||
},
|
||||
undefined,
|
||||
"Hello world"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 13,
|
||||
"insertText": "\n"
|
||||
},
|
||||
undefined,
|
||||
"Hello world.\n"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 13,
|
||||
"insertText": "\n"
|
||||
},
|
||||
"\n",
|
||||
"Hello world.\n"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 13,
|
||||
"insertText": "\n"
|
||||
},
|
||||
"\r\n",
|
||||
"Hello world.\r\n"
|
||||
]
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
const [ line, fixInfo, lineEnding, expected ] = testCase;
|
||||
// @ts-ignore
|
||||
const actual = markdownlint.applyFix(line, fixInfo, lineEnding);
|
||||
t.is(actual, String(expected), "Incorrect fix applied.");
|
||||
}
|
||||
});
|
||||
|
||||
test("applyFixes", (t) => {
|
||||
t.plan(30);
|
||||
const testCases = [
|
||||
[
|
||||
"Hello world.",
|
||||
[],
|
||||
"Hello world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {}
|
||||
}
|
||||
],
|
||||
"Hello world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"insertText": "Very "
|
||||
}
|
||||
}
|
||||
],
|
||||
"Very Hello world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"insertText": "big "
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello big world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"deleteCount": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 5,
|
||||
"insertText": "there"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello there."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 12,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 6,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Helloworld"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 13,
|
||||
"insertText": " Hi."
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello world. Hi."
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"world"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 2,
|
||||
"fixInfo": {
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 2,
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"world"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"lineNumber": 2,
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 4,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 10,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Helo word"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 10,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 4,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Helo word"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"deleteCount": -1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "Big "
|
||||
}
|
||||
}
|
||||
],
|
||||
"world"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"deleteCount": -1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 2,
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
""
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "aa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"aaHello world"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "bb"
|
||||
}
|
||||
}
|
||||
],
|
||||
"bbHello world"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 6,
|
||||
"insertText": " big"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello big orld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 8,
|
||||
"deleteCount": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello wld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 8,
|
||||
"deleteCount": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello wld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1,
|
||||
"insertText": "z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello zorld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"insertText": "z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello zorld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"insertText": "z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello zorld"
|
||||
],
|
||||
[
|
||||
"Hello\nworld\nhello\rworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 4,
|
||||
"editColumn": 6,
|
||||
"insertText": "\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\nworld\nhello\nworld\n"
|
||||
],
|
||||
[
|
||||
"Hello\r\nworld\r\nhello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 4,
|
||||
"editColumn": 6,
|
||||
"insertText": "\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\r\nworld\r\nhello\r\nworld\r\n"
|
||||
],
|
||||
[
|
||||
"Hello\rworld\rhello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 4,
|
||||
"editColumn": 6,
|
||||
"insertText": "\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\rworld\rhello\rworld\r"
|
||||
],
|
||||
[
|
||||
"Hello\r\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 2,
|
||||
"fixInfo": {
|
||||
"editColumn": 6,
|
||||
"insertText": "\n\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\r\nworld\r\n\r\n"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"insertText": "x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
""
|
||||
],
|
||||
[
|
||||
" hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 1,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 2,
|
||||
"deleteCount": 1,
|
||||
"insertText": "H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello world"
|
||||
]
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
const [ input, errors, expected ] = testCase;
|
||||
// @ts-ignore
|
||||
const actual = markdownlint.applyFixes(input, errors);
|
||||
t.is(actual, String(expected), "Incorrect fix applied.");
|
||||
}
|
||||
});
|
|
@ -6,7 +6,8 @@ const os = require("node:os");
|
|||
const path = require("node:path");
|
||||
const test = require("ava").default;
|
||||
const helpers = require("../helpers");
|
||||
const { markdownlint } = require("../lib/markdownlint").promises;
|
||||
const libMarkdownlint = require("../lib/markdownlint");
|
||||
const { markdownlint } = libMarkdownlint.promises;
|
||||
const { forEachInlineCodeSpan } = require("../lib/markdownit.cjs");
|
||||
|
||||
test("clearHtmlCommentTextValid", (t) => {
|
||||
|
@ -352,532 +353,6 @@ test("getPreferredLineEnding", (t) => {
|
|||
t.is(helpers.getPreferredLineEnding("", { "EOL": "custom" }), "custom");
|
||||
});
|
||||
|
||||
test("applyFix", (t) => {
|
||||
t.plan(4);
|
||||
const testCases = [
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 12,
|
||||
"deleteCount": 1
|
||||
},
|
||||
undefined,
|
||||
"Hello world"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 13,
|
||||
"insertText": "\n"
|
||||
},
|
||||
undefined,
|
||||
"Hello world.\n"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 13,
|
||||
"insertText": "\n"
|
||||
},
|
||||
"\n",
|
||||
"Hello world.\n"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
{
|
||||
"editColumn": 13,
|
||||
"insertText": "\n"
|
||||
},
|
||||
"\r\n",
|
||||
"Hello world.\r\n"
|
||||
]
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
const [ line, fixInfo, lineEnding, expected ] = testCase;
|
||||
// @ts-ignore
|
||||
const actual = helpers.applyFix(line, fixInfo, lineEnding);
|
||||
t.is(actual, String(expected), "Incorrect fix applied.");
|
||||
}
|
||||
});
|
||||
|
||||
test("applyFixes", (t) => {
|
||||
t.plan(30);
|
||||
const testCases = [
|
||||
[
|
||||
"Hello world.",
|
||||
[],
|
||||
"Hello world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {}
|
||||
}
|
||||
],
|
||||
"Hello world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"insertText": "Very "
|
||||
}
|
||||
}
|
||||
],
|
||||
"Very Hello world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"insertText": "big "
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello big world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"deleteCount": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
"world."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 7,
|
||||
"deleteCount": 5,
|
||||
"insertText": "there"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello there."
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 12,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 6,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Helloworld"
|
||||
],
|
||||
[
|
||||
"Hello world.",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 13,
|
||||
"insertText": " Hi."
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello world. Hi."
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"world"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 2,
|
||||
"fixInfo": {
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 2,
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"world"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"lineNumber": 2,
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 4,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 10,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Helo word"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 10,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 4,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Helo word"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"deleteCount": -1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "Big "
|
||||
}
|
||||
}
|
||||
],
|
||||
"world"
|
||||
],
|
||||
[
|
||||
"Hello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"deleteCount": -1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 2,
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
""
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "aa"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"aaHello world"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"insertText": "bb"
|
||||
}
|
||||
}
|
||||
],
|
||||
"bbHello world"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 6,
|
||||
"insertText": " big"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello big orld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 8,
|
||||
"deleteCount": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello wld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 8,
|
||||
"deleteCount": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello wld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1,
|
||||
"insertText": "z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello zorld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"insertText": "z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello zorld"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"insertText": "z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 1,
|
||||
"editColumn": 7,
|
||||
"deleteCount": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello zorld"
|
||||
],
|
||||
[
|
||||
"Hello\nworld\nhello\rworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 4,
|
||||
"editColumn": 6,
|
||||
"insertText": "\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\nworld\nhello\nworld\n"
|
||||
],
|
||||
[
|
||||
"Hello\r\nworld\r\nhello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 4,
|
||||
"editColumn": 6,
|
||||
"insertText": "\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\r\nworld\r\nhello\r\nworld\r\n"
|
||||
],
|
||||
[
|
||||
"Hello\rworld\rhello\nworld",
|
||||
[
|
||||
{
|
||||
"fixInfo": {
|
||||
"lineNumber": 4,
|
||||
"editColumn": 6,
|
||||
"insertText": "\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\rworld\rhello\rworld\r"
|
||||
],
|
||||
[
|
||||
"Hello\r\nworld",
|
||||
[
|
||||
{
|
||||
"lineNumber": 2,
|
||||
"fixInfo": {
|
||||
"editColumn": 6,
|
||||
"insertText": "\n\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello\r\nworld\r\n\r\n"
|
||||
],
|
||||
[
|
||||
"Hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"insertText": "x"
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"deleteCount": -1
|
||||
}
|
||||
}
|
||||
],
|
||||
""
|
||||
],
|
||||
[
|
||||
" hello world",
|
||||
[
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 1,
|
||||
"deleteCount": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"lineNumber": 1,
|
||||
"fixInfo": {
|
||||
"editColumn": 2,
|
||||
"deleteCount": 1,
|
||||
"insertText": "H"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Hello world"
|
||||
]
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
const [ input, errors, expected ] = testCase;
|
||||
// @ts-ignore
|
||||
const actual = helpers.applyFixes(input, errors);
|
||||
t.is(actual, String(expected), "Incorrect fix applied.");
|
||||
}
|
||||
});
|
||||
|
||||
test("expandTildePath", (t) => {
|
||||
t.plan(17);
|
||||
const homedir = os.homedir();
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
const fs = require("node:fs").promises;
|
||||
const path = require("node:path");
|
||||
const test = require("ava").default;
|
||||
const { markdownlint } = require("../lib/markdownlint").promises;
|
||||
const libMarkdownlint = require("../lib/markdownlint");
|
||||
const { applyFixes, promises } = libMarkdownlint;
|
||||
const { markdownlint } = promises;
|
||||
const helpers = require("../helpers");
|
||||
const constants = require("../lib/constants");
|
||||
|
||||
|
@ -82,7 +84,7 @@ function createTestForFile(file) {
|
|||
}
|
||||
t.deepEqual(actual, expected, "Too few or too many issues found.");
|
||||
// Create snapshot
|
||||
const fixed = helpers.applyFixes(content, errors)
|
||||
const fixed = applyFixes(content, errors)
|
||||
.replace(/\r\n/g, "\n");
|
||||
t.snapshot({
|
||||
errors,
|
||||
|
|
|
@ -686,8 +686,8 @@ test("readmeHeadings", (t) => new Promise((resolve) => {
|
|||
"#### fs",
|
||||
"#### callback",
|
||||
"#### result",
|
||||
"## Usage",
|
||||
"### Fixing",
|
||||
"## Usage",
|
||||
"## Browser",
|
||||
"## Examples",
|
||||
"## Contributing",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue