mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-16 05:50:13 +01:00
Reimplement MD007/ul-indent using micromark tokens, reduce the length of fix edits (fixes #969).
This commit is contained in:
parent
cdf0818752
commit
da17cec722
11 changed files with 1138 additions and 238 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
75
lib/md007.js
75
lib/md007.js
|
|
@ -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
63
test/lists-on-a-line.md
Normal 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
|
||||||
|
} -->
|
||||||
|
|
@ -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
Binary file not shown.
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load diff
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue