Reimplement MD007/ul-indent using micromark tokens, reduce the length of fix edits (fixes #969).

This commit is contained in:
David Anson 2023-10-15 21:24:59 -07:00
parent cdf0818752
commit da17cec722
11 changed files with 1138 additions and 238 deletions

View file

@ -1204,6 +1204,14 @@ var flatTokensSymbol = Symbol("flat-tokens");
* @property {number} endColumn End column (1-based). * @property {number} endColumn End column (1-based).
* @property {string} text Token text. * @property {string} text Token text.
* @property {Token[]} children Child tokens. * @property {Token[]} children Child tokens.
* @property {GetTokenParent} parent Parent token.
*/
/**
* Returns parent Token of a Token.
*
* @typedef {Function} GetTokenParent
* @returns {Token} Parent token.
*/ */
/** /**
@ -1276,17 +1284,18 @@ function micromarkParseWithOffset(markdown, micromarkOptions, referencesDefined,
// Create Token objects // Create Token objects
var document = []; var document = [];
var flatTokens = []; var flatTokens = [];
var current = { var root = {
"children": document "children": document
}; };
var history = [current]; var history = [root];
var current = root;
var reparseOptions = null; var reparseOptions = null;
var lines = null; var lines = null;
var skipHtmlFlowChildren = false; var skipHtmlFlowChildren = false;
var _iterator = _createForOfIteratorHelper(events), var _iterator = _createForOfIteratorHelper(events),
_step; _step;
try { try {
for (_iterator.s(); !(_step = _iterator.n()).done;) { var _loop = function _loop() {
var event = _step.value; var event = _step.value;
var _event = _slicedToArray(event, 3), var _event = _slicedToArray(event, 3),
kind = _event[0], kind = _event[0],
@ -1310,7 +1319,10 @@ function micromarkParseWithOffset(markdown, micromarkOptions, referencesDefined,
"endLine": endLine + lineDelta, "endLine": endLine + lineDelta,
endColumn: endColumn, endColumn: endColumn,
text: text, text: text,
"children": [] "children": [],
"parent": function parent() {
return previous === root ? null : previous;
}
}; };
previous.children.push(current); previous.children.push(current);
flatTokens.push(current); flatTokens.push(current);
@ -1345,6 +1357,9 @@ function micromarkParseWithOffset(markdown, micromarkOptions, referencesDefined,
current = history.pop(); current = history.pop();
} }
} }
};
for (_iterator.s(); !(_step = _iterator.n()).done;) {
_loop();
} }
// Return document // Return document
@ -3542,11 +3557,9 @@ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"),
addErrorDetailIf = _require.addErrorDetailIf, addErrorDetailIf = _require.addErrorDetailIf;
indentFor = _require.indentFor, var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"),
listItemMarkerRe = _require.listItemMarkerRe; filterByTypes = _require2.filterByTypes;
var _require2 = __webpack_require__(/*! ./cache */ "../lib/cache.js"),
flattenedLists = _require2.flattenedLists;
module.exports = { module.exports = {
"names": ["MD007", "ul-indent"], "names": ["MD007", "ul-indent"],
"description": "Unordered list indentation", "description": "Unordered list indentation",
@ -3555,38 +3568,51 @@ module.exports = {
var indent = Number(params.config.indent || 2); var indent = Number(params.config.indent || 2);
var startIndented = !!params.config.start_indented; var startIndented = !!params.config.start_indented;
var startIndent = Number(params.config.start_indent || indent); var startIndent = Number(params.config.start_indent || indent);
var _iterator = _createForOfIteratorHelper(flattenedLists()), var unorderedListNesting = new Map();
var lastBlockQuotePrefix = null;
var tokens = filterByTypes(params.parsers.micromark.tokens, ["blockQuotePrefix", "listItemPrefix", "listUnordered"]);
var _iterator = _createForOfIteratorHelper(tokens),
_step; _step;
try { try {
for (_iterator.s(); !(_step = _iterator.n()).done;) { for (_iterator.s(); !(_step = _iterator.n()).done;) {
var list = _step.value; var token = _step.value;
if (list.unordered && list.parentsUnordered) { var startColumn = token.startColumn,
var _iterator2 = _createForOfIteratorHelper(list.items), startLine = token.startLine,
_step2; type = token.type;
try { if (type === "blockQuotePrefix") {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { lastBlockQuotePrefix = token;
var item = _step2.value; } else if (type === "listUnordered") {
var lineNumber = item.lineNumber, var nesting = 0;
line = item.line; var current = token;
var expectedIndent = (startIndented ? startIndent : 0) + list.nesting * indent; while (current = current.parent()) {
var actualIndent = indentFor(item); if (current.type === "listUnordered") {
var range = null; nesting++;
var editColumn = 1; } else if (current.type === "listOrdered") {
var match = line.match(listItemMarkerRe); nesting = -1;
if (match) { break;
range = [1, match[0].length]; } else if (current.type === "blockQuote") {
editColumn += match[1].length - actualIndent; break;
}
addErrorDetailIf(onError, lineNumber, expectedIndent, actualIndent, null, null, range, {
editColumn: editColumn,
"deleteCount": actualIndent,
"insertText": "".padEnd(expectedIndent)
});
} }
} catch (err) { }
_iterator2.e(err); if (nesting >= 0) {
} finally { unorderedListNesting.set(token, nesting);
_iterator2.f(); }
} else {
// listItemPrefix
var _nesting = unorderedListNesting.get(token.parent());
if (_nesting !== undefined) {
var _lastBlockQuotePrefix;
// listItemPrefix for listUnordered
var expectedIndent = (startIndented ? startIndent : 0) + _nesting * indent;
var blockQuoteAdjustment = ((_lastBlockQuotePrefix = lastBlockQuotePrefix) === null || _lastBlockQuotePrefix === void 0 ? void 0 : _lastBlockQuotePrefix.endLine) === startLine ? lastBlockQuotePrefix.endColumn - 1 : 0;
var actualIndent = startColumn - 1 - blockQuoteAdjustment;
var range = [1, startColumn + 1];
var fixInfo = {
"editColumn": startColumn - actualIndent,
"deleteCount": Math.max(actualIndent - expectedIndent, 0),
"insertText": "".padEnd(Math.max(expectedIndent - actualIndent, 0))
};
addErrorDetailIf(onError, startLine, expectedIndent, actualIndent, undefined, undefined, range, fixInfo);
} }
} }
} }

View file

@ -23,6 +23,14 @@ const flatTokensSymbol = Symbol("flat-tokens");
* @property {number} endColumn End column (1-based). * @property {number} endColumn End column (1-based).
* @property {string} text Token text. * @property {string} text Token text.
* @property {Token[]} children Child tokens. * @property {Token[]} children Child tokens.
* @property {GetTokenParent} parent Parent token.
*/
/**
* Returns parent Token of a Token.
*
* @typedef {Function} GetTokenParent
* @returns {Token} Parent token.
*/ */
/** /**
@ -113,10 +121,11 @@ function micromarkParseWithOffset(
// Create Token objects // Create Token objects
const document = []; const document = [];
let flatTokens = []; let flatTokens = [];
let current = { const root = {
"children": document "children": document
}; };
const history = [ current ]; const history = [ root ];
let current = root;
let reparseOptions = null; let reparseOptions = null;
let lines = null; let lines = null;
let skipHtmlFlowChildren = false; let skipHtmlFlowChildren = false;
@ -136,7 +145,8 @@ function micromarkParseWithOffset(
"endLine": endLine + lineDelta, "endLine": endLine + lineDelta,
endColumn, endColumn,
text, text,
"children": [] "children": [],
"parent": () => (previous === root ? null : previous)
}; };
previous.children.push(current); previous.children.push(current);
flatTokens.push(current); flatTokens.push(current);

View file

@ -2,9 +2,8 @@
"use strict"; "use strict";
const { addErrorDetailIf, indentFor, listItemMarkerRe } = const { addErrorDetailIf } = require("../helpers");
require("../helpers"); const { filterByTypes } = require("../helpers/micromark.cjs");
const { flattenedLists } = require("./cache");
module.exports = { module.exports = {
"names": [ "MD007", "ul-indent" ], "names": [ "MD007", "ul-indent" ],
@ -14,34 +13,60 @@ module.exports = {
const indent = Number(params.config.indent || 2); const indent = Number(params.config.indent || 2);
const startIndented = !!params.config.start_indented; const startIndented = !!params.config.start_indented;
const startIndent = Number(params.config.start_indent || indent); const startIndent = Number(params.config.start_indent || indent);
for (const list of flattenedLists()) { const unorderedListNesting = new Map();
if (list.unordered && list.parentsUnordered) { let lastBlockQuotePrefix = null;
for (const item of list.items) { const tokens = filterByTypes(
const { lineNumber, line } = item; params.parsers.micromark.tokens,
const expectedIndent = [ "blockQuotePrefix", "listItemPrefix", "listUnordered" ]
(startIndented ? startIndent : 0) + );
(list.nesting * indent); for (const token of tokens) {
const actualIndent = indentFor(item); const { startColumn, startLine, type } = token;
let range = null; if (type === "blockQuotePrefix") {
let editColumn = 1; lastBlockQuotePrefix = token;
const match = line.match(listItemMarkerRe); } else if (type === "listUnordered") {
if (match) { let nesting = 0;
range = [ 1, match[0].length ]; let current = token;
editColumn += match[1].length - actualIndent; while ((current = current.parent())) {
if (current.type === "listUnordered") {
nesting++;
} else if (current.type === "listOrdered") {
nesting = -1;
break;
} else if (current.type === "blockQuote") {
break;
} }
}
if (nesting >= 0) {
unorderedListNesting.set(token, nesting);
}
} else {
// listItemPrefix
const nesting = unorderedListNesting.get(token.parent());
if (nesting !== undefined) {
// listItemPrefix for listUnordered
const expectedIndent =
(startIndented ? startIndent : 0) + (nesting * indent);
const blockQuoteAdjustment =
(lastBlockQuotePrefix?.endLine === startLine) ?
(lastBlockQuotePrefix.endColumn - 1) :
0;
const actualIndent = startColumn - 1 - blockQuoteAdjustment;
const range = [ 1, startColumn + 1 ];
const fixInfo = {
"editColumn": startColumn - actualIndent,
"deleteCount": Math.max(actualIndent - expectedIndent, 0),
"insertText": "".padEnd(Math.max(expectedIndent - actualIndent, 0))
};
addErrorDetailIf( addErrorDetailIf(
onError, onError,
lineNumber, startLine,
expectedIndent, expectedIndent,
actualIndent, actualIndent,
null, undefined,
null, undefined,
range, range,
{ fixInfo
editColumn, );
"deleteCount": actualIndent,
"insertText": "".padEnd(expectedIndent)
});
} }
} }
} }

63
test/lists-on-a-line.md Normal file
View file

@ -0,0 +1,63 @@
# Lists on a Line
## Correct
Text
* * Item
Text
* * * Item
Text
- + * Item
Text
- 1. Item
Text
- 1. + Item
Text
* * * Item
* Item
* Item
* Item
## Incorrect
Text
* * Item {MD007}
Text
* * * Item {MD007}
Text
- + * Item {MD007}
Text
- 1. Item {MD007}
Text
- 1. + Item {MD007}
Text
* * * Item {MD007}
* Item {MD005} {MD007}
* Item {MD005} {MD007}
* Item {MD005} {MD007}
<!-- markdownlint-configure-file {
"ul-style": false
} -->

View file

@ -914,7 +914,7 @@ test("readme", async(t) => {
}); });
test("validateJsonUsingConfigSchemaStrict", async(t) => { test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(161); t.plan(162);
const { addSchema, validate } = const { addSchema, validate } =
// eslint-disable-next-line n/file-extension-in-import // eslint-disable-next-line n/file-extension-in-import
await import("@hyperjump/json-schema/draft-07"); await import("@hyperjump/json-schema/draft-07");

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,10 @@ Generated by [AVA](https://avajs.dev).
> Expected linting violations > Expected linting violations
'' `test-repos/mdn-content/files/en-us/web/svg/attribute/d/index.md: 234: MD007/ul-indent Unordered list indentation [Expected: 0; Actual: 10]␊
test-repos/mdn-content/files/en-us/web/svg/attribute/d/index.md: 556: MD007/ul-indent Unordered list indentation [Expected: 0; Actual: 12]␊
test-repos/mdn-content/files/en-us/web/svg/attribute/d/index.md: 769: MD007/ul-indent Unordered list indentation [Expected: 0; Actual: 12]␊
test-repos/mdn-content/files/en-us/web/svg/attribute/d/index.md: 838: MD007/ul-indent Unordered list indentation [Expected: 0; Actual: 12]`
## https://github.com/mkdocs/mkdocs ## https://github.com/mkdocs/mkdocs

File diff suppressed because it is too large Load diff