feat: implement autofix for md029

Add support for auto-fixing MD029 violations.

Close #949
This commit is contained in:
Marco Ferrari 2025-08-20 16:24:46 +00:00
parent 3532e3110a
commit cec6c7e95d
12 changed files with 1018 additions and 177 deletions

View file

@ -1046,6 +1046,8 @@ Parameters:
- `style`: List style (`string`, default `one_or_ordered`, values `one` / - `style`: List style (`string`, default `one_or_ordered`, values `one` /
`one_or_ordered` / `ordered` / `zero`) `one_or_ordered` / `ordered` / `zero`)
Fixable: Some violations can be fixed by tooling
This rule is triggered for ordered lists that do not either start with '1.' or This rule is triggered for ordered lists that do not either start with '1.' or
do not have a prefix that increases in numerical order (depending on the do not have a prefix that increases in numerical order (depending on the
configured style). The less-common pattern of using '0.' as a first prefix or configured style). The less-common pattern of using '0.' as a first prefix or

View file

@ -9,6 +9,8 @@ Parameters:
- `style`: List style (`string`, default `one_or_ordered`, values `one` / - `style`: List style (`string`, default `one_or_ordered`, values `one` /
`one_or_ordered` / `ordered` / `zero`) `one_or_ordered` / `ordered` / `zero`)
Fixable: Some violations can be fixed by tooling
This rule is triggered for ordered lists that do not either start with '1.' or This rule is triggered for ordered lists that do not either start with '1.' or
do not have a prefix that increases in numerical order (depending on the do not have a prefix that increases in numerical order (depending on the
configured style). The less-common pattern of using '0.' as a first prefix or configured style). The less-common pattern of using '0.' as a first prefix or

View file

@ -4,10 +4,10 @@ export const deprecatedRuleNames = [];
export const fixableRuleNames = [ export const fixableRuleNames = [
"MD004", "MD005", "MD007", "MD009", "MD010", "MD011", "MD004", "MD005", "MD007", "MD009", "MD010", "MD011",
"MD012", "MD014", "MD018", "MD019", "MD020", "MD021", "MD012", "MD014", "MD018", "MD019", "MD020", "MD021",
"MD022", "MD023", "MD026", "MD027", "MD030", "MD031", "MD022", "MD023", "MD026", "MD027", "MD029", "MD030",
"MD032", "MD034", "MD037", "MD038", "MD039", "MD044", "MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
"MD047", "MD049", "MD050", "MD051", "MD053", "MD054", "MD044", "MD047", "MD049", "MD050", "MD051", "MD053",
"MD058" "MD054", "MD058"
]; ];
export const homepage = "https://github.com/DavidAnson/markdownlint"; export const homepage = "https://github.com/DavidAnson/markdownlint";
export const version = "0.38.0"; export const version = "0.38.0";

View file

@ -14,10 +14,14 @@ const listStyleExamples = {
* Gets the value of an ordered list item prefix token. * Gets the value of an ordered list item prefix token.
* *
* @param {import("markdownlint").MicromarkToken} listItemPrefix List item prefix token. * @param {import("markdownlint").MicromarkToken} listItemPrefix List item prefix token.
* @returns {number} List item value. * @returns {{column: number, value: number}} List item column, and value.
*/ */
function getOrderedListItemValue(listItemPrefix) { function getOrderedListItemValue(listItemPrefix) {
return Number(getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0].text); const listItemValue = getDescendantsByType(listItemPrefix, [ "listItemValue" ])[0];
return {
"column": listItemValue.startColumn,
"value": Number(listItemValue.text)
};
} }
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
@ -34,11 +38,11 @@ export default {
let incrementing = false; let incrementing = false;
// Check for incrementing number pattern 1/2/3 or 0/1/2 // Check for incrementing number pattern 1/2/3 or 0/1/2
if (listItemPrefixes.length >= 2) { if (listItemPrefixes.length >= 2) {
const firstValue = getOrderedListItemValue(listItemPrefixes[0]); const first = getOrderedListItemValue(listItemPrefixes[0]);
const secondValue = getOrderedListItemValue(listItemPrefixes[1]); const second = getOrderedListItemValue(listItemPrefixes[1]);
if ((secondValue !== 1) || (firstValue === 0)) { if ((second.value !== 1) || (first.value === 0)) {
incrementing = true; incrementing = true;
if (firstValue === 0) { if (first.value === 0) {
expected = 0; expected = 0;
} }
} }
@ -54,7 +58,13 @@ export default {
} }
// Validate each list item marker // Validate each list item marker
for (const listItemPrefix of listItemPrefixes) { for (const listItemPrefix of listItemPrefixes) {
const actual = getOrderedListItemValue(listItemPrefix); const orderedListItemValue = getOrderedListItemValue(listItemPrefix);
const actual = orderedListItemValue.value;
const fixInfo = {
"editColumn": orderedListItemValue.column,
"deleteCount": orderedListItemValue.value.toString().length,
"insertText": expected.toString()
};
addErrorDetailIf( addErrorDetailIf(
onError, onError,
listItemPrefix.startLine, listItemPrefix.startLine,
@ -62,7 +72,8 @@ export default {
actual, actual,
"Style: " + listStyleExamples[listStyle], "Style: " + listStyleExamples[listStyle],
undefined, undefined,
[ listItemPrefix.startColumn, listItemPrefix.endColumn - listItemPrefix.startColumn ] [ listItemPrefix.startColumn, listItemPrefix.endColumn - listItemPrefix.startColumn ],
fixInfo
); );
if (listStyle === "ordered") { if (listStyle === "ordered") {
expected++; expected++;

View file

@ -912,7 +912,7 @@ test("readme", async(t) => {
}); });
test("validateJsonUsingConfigSchemaStrict", async(t) => { test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(209); t.plan(211);
// @ts-ignore // @ts-ignore
const ajv = new Ajv(ajvOptions); const ajv = new Ajv(ajvOptions);
const validateSchemaStrict = ajv.compile(configSchemaStrict); const validateSchemaStrict = ajv.compile(configSchemaStrict);

View file

@ -0,0 +1,42 @@
# Ordered list examples
Good list:
1. Item
2. Item
3. Item
4. Item
5. Item
6. Item
7. Item
8. Item
9. Item
10. Item
11. Item
Bad list 1:
1. Item
10. Item {MD029}
9. Item {MD029}
Bad list 2:
11. Item {MD029}
10. Item {MD029}
Bad list 3
12. Item {MD029}
1. Item {MD029}
Bad list 4:
0. Item
10. Item {MD029}
<!-- markdownlint-configure-file {
"ol-prefix": {
"style": "ordered"
}
} -->

View file

@ -0,0 +1,47 @@
# Ordered list examples
Good list and sublist:
1. Item
2. Item
1. Item
2. Item
3. Item
Good list and bad sublist:
1. Item
2. Item
3. Item
4. Item
3. Item
Bad list and good sublist:
1. Item
4. Item {MD029}
1. Item
2. Item
5. Item {MD029}
Bad list and bad sublist:
1. Item
4. Item {MD029}
1. Item
3. Item {MD029}
5. Item {MD029}
Bad list and bad sublist (0):
0. Item
4. Item {MD029}
1. Item
3. Item {MD029}
5. Item {MD029}
<!-- markdownlint-configure-file {
"ol-prefix": {
"style": "ordered"
}
} -->

File diff suppressed because it is too large Load diff