mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-17 06:20:12 +01:00
Add MD030 with tests, refactor list handling with flattenLists.
This commit is contained in:
parent
b21548a992
commit
2b289ab5f3
4 changed files with 177 additions and 56 deletions
|
|
@ -58,7 +58,7 @@
|
||||||
"no-multiple-empty-lines": [2, { "max": 2 }],
|
"no-multiple-empty-lines": [2, { "max": 2 }],
|
||||||
"no-native-reassign": 2,
|
"no-native-reassign": 2,
|
||||||
"no-negated-in-lhs": 2,
|
"no-negated-in-lhs": 2,
|
||||||
"no-nested-ternary": 2,
|
"no-nested-ternary": 0,
|
||||||
"no-new": 2,
|
"no-new": 2,
|
||||||
"no-new-func": 2,
|
"no-new-func": 2,
|
||||||
"no-new-object": 2,
|
"no-new-object": 2,
|
||||||
|
|
|
||||||
133
lib/rules.js
133
lib/rules.js
|
|
@ -18,14 +18,13 @@ function headingStyleFor(token) {
|
||||||
|
|
||||||
function unorderedListStyleFor(token) {
|
function unorderedListStyleFor(token) {
|
||||||
switch (token.line.trimLeft().substr(0, 1)) {
|
switch (token.line.trimLeft().substr(0, 1)) {
|
||||||
case "*":
|
|
||||||
return "asterisk";
|
|
||||||
case "-":
|
case "-":
|
||||||
return "dash";
|
return "dash";
|
||||||
case "+":
|
case "+":
|
||||||
return "plus";
|
return "plus";
|
||||||
|
case "*":
|
||||||
default:
|
default:
|
||||||
return null;
|
return "asterisk";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +53,40 @@ function forEachLine(params, callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flattenLists(tokens, filterBy) {
|
||||||
|
var lists = [];
|
||||||
|
var stack = [];
|
||||||
|
var current = null;
|
||||||
|
var lastWithLines = null;
|
||||||
|
tokens.forEach(function forToken(token) {
|
||||||
|
if ((token.type === "bullet_list_open") ||
|
||||||
|
(token.type === "ordered_list_open")) {
|
||||||
|
stack.push(current);
|
||||||
|
current = {
|
||||||
|
"ordered": (token.type === "ordered_list_open"),
|
||||||
|
"open": token,
|
||||||
|
"items": [],
|
||||||
|
"nesting": stack.length - 1,
|
||||||
|
"lastLineIndex": -1,
|
||||||
|
"insert": lists.length
|
||||||
|
};
|
||||||
|
} else if ((token.type === "bullet_list_close") ||
|
||||||
|
(token.type === "ordered_list_close")) {
|
||||||
|
current.lastLineIndex = lastWithLines.lines[1];
|
||||||
|
if ((filterBy === undefined) || (filterBy === current.ordered)) {
|
||||||
|
lists.splice(current.insert, 0, current);
|
||||||
|
delete current.insert;
|
||||||
|
}
|
||||||
|
current = stack.pop();
|
||||||
|
} else if (token.type === "list_item_open") {
|
||||||
|
current.items.push(token);
|
||||||
|
} else if (token.lines) {
|
||||||
|
lastWithLines = token;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return lists;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
"name": "MD001",
|
"name": "MD001",
|
||||||
|
|
@ -108,15 +141,16 @@ module.exports = [
|
||||||
"desc": "Unordered list style",
|
"desc": "Unordered list style",
|
||||||
"func": function MD004(params, errors) {
|
"func": function MD004(params, errors) {
|
||||||
var style = params.options.style || "consistent";
|
var style = params.options.style || "consistent";
|
||||||
var listItems = filterTokens(params.tokens, "list_item_open");
|
flattenLists(params.tokens, false).forEach(function forList(list) {
|
||||||
if ((style === "consistent") && listItems.length) {
|
if (style === "consistent") {
|
||||||
style = unorderedListStyleFor(listItems[0]);
|
style = unorderedListStyleFor(list.items[0]);
|
||||||
}
|
}
|
||||||
listItems.forEach(function forToken(token) {
|
list.items.forEach(function forItem(item) {
|
||||||
if (unorderedListStyleFor(token) !== style) {
|
if (unorderedListStyleFor(item) !== style) {
|
||||||
errors.push(token.lineNumber);
|
errors.push(item.lineNumber);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -124,16 +158,14 @@ module.exports = [
|
||||||
"name": "MD005",
|
"name": "MD005",
|
||||||
"desc": "Inconsistent indentation for list items at the same level",
|
"desc": "Inconsistent indentation for list items at the same level",
|
||||||
"func": function MD005(params, errors) {
|
"func": function MD005(params, errors) {
|
||||||
var indentLevels = [];
|
flattenLists(params.tokens).forEach(function forList(list) {
|
||||||
filterTokens(params.tokens, "list_item_open")
|
var indent = indentFor(list.items[0]);
|
||||||
.forEach(function forToken(token) {
|
list.items.forEach(function forItem(item) {
|
||||||
var indentLevel = indentFor(token);
|
if (indentFor(item) !== indent) {
|
||||||
if (!indentLevels[token.level]) {
|
errors.push(item.lineNumber);
|
||||||
indentLevels[token.level] = indentLevel;
|
|
||||||
} else if (indentLevel !== indentLevels[token.level]) {
|
|
||||||
errors.push(token.lineNumber);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -141,21 +173,9 @@ module.exports = [
|
||||||
"name": "MD006",
|
"name": "MD006",
|
||||||
"desc": "Consider starting bulleted lists at the beginning of the line",
|
"desc": "Consider starting bulleted lists at the beginning of the line",
|
||||||
"func": function MD006(params, errors) {
|
"func": function MD006(params, errors) {
|
||||||
var inList = 0;
|
flattenLists(params.tokens, false).forEach(function forList(list) {
|
||||||
params.tokens.filter(function filterToken(token) {
|
if (!list.nesting && indentFor(list.open)) {
|
||||||
switch (token.type) {
|
errors.push(list.open.lineNumber);
|
||||||
case "bullet_list_open":
|
|
||||||
inList++;
|
|
||||||
return inList === 1;
|
|
||||||
case "bullet_list_close":
|
|
||||||
inList--;
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}).forEach(function forToken(token) {
|
|
||||||
if (indentFor(token) !== 0) {
|
|
||||||
errors.push(token.lineNumber);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -167,20 +187,17 @@ module.exports = [
|
||||||
"func": function MD007(params, errors) {
|
"func": function MD007(params, errors) {
|
||||||
var optionsIndent = params.options.indent || 2;
|
var optionsIndent = params.options.indent || 2;
|
||||||
var prevIndent = 0;
|
var prevIndent = 0;
|
||||||
filterTokens(params.tokens, "bullet_list_open")
|
flattenLists(params.tokens, false).forEach(function forList(list) {
|
||||||
.forEach(function forToken(token) {
|
var indent = indentFor(list.open);
|
||||||
var indent = indentFor(token);
|
|
||||||
if ((indent > prevIndent) &&
|
if ((indent > prevIndent) &&
|
||||||
((indent - prevIndent) !== optionsIndent)) {
|
((indent - prevIndent) !== optionsIndent)) {
|
||||||
errors.push(token.lineNumber);
|
errors.push(list.open.lineNumber);
|
||||||
}
|
}
|
||||||
prevIndent = indent;
|
prevIndent = indent;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// MD008 does not exist
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "MD009",
|
"name": "MD009",
|
||||||
"desc": "Trailing spaces",
|
"desc": "Trailing spaces",
|
||||||
|
|
@ -378,21 +395,41 @@ module.exports = [
|
||||||
"desc": "Ordered list item prefix",
|
"desc": "Ordered list item prefix",
|
||||||
"func": function MD029(params, errors) {
|
"func": function MD029(params, errors) {
|
||||||
var style = params.options.style || "one";
|
var style = params.options.style || "one";
|
||||||
var number = 0;
|
flattenLists(params.tokens, true).forEach(function forList(list) {
|
||||||
params.tokens.forEach(function forToken(token) {
|
var number = 1;
|
||||||
if (token.type === "ordered_list_open") {
|
list.items.forEach(function forItem(item) {
|
||||||
number = 1;
|
var re = new RegExp("^\\s*" + String(number) + "\\. ");
|
||||||
} else if (token.type === "ordered_list_close") {
|
if (!re.test(item.line)) {
|
||||||
number = 0;
|
errors.push(item.lineNumber);
|
||||||
} else if ((token.type === "list_item_open") && number) {
|
|
||||||
var regex = new RegExp("^\\s*" + String(number) + "\\. ");
|
|
||||||
if (!regex.test(token.line)) {
|
|
||||||
errors.push(token.lineNumber);
|
|
||||||
}
|
}
|
||||||
if (style === "ordered") {
|
if (style === "ordered") {
|
||||||
number++;
|
number++;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "MD030",
|
||||||
|
"desc": "Spaces after list markers",
|
||||||
|
"func": function MD030(params, errors) {
|
||||||
|
var ulSingle = params.options.ul_single || 1;
|
||||||
|
var olSingle = params.options.ol_single || 1;
|
||||||
|
var ulMulti = params.options.ul_multi || 1;
|
||||||
|
var olMulti = params.options.ol_multi || 1;
|
||||||
|
flattenLists(params.tokens).forEach(function forList(list) {
|
||||||
|
var lineCount = list.lastLineIndex - list.open.lines[0];
|
||||||
|
var allSingle = lineCount === list.items.length;
|
||||||
|
var expectedSpaces = list.ordered ?
|
||||||
|
(allSingle ? olSingle : olMulti) :
|
||||||
|
(allSingle ? ulSingle : ulMulti);
|
||||||
|
list.items.forEach(function forItem(item) {
|
||||||
|
var match = /^\s*\S+(\s+)/.exec(item.line);
|
||||||
|
if (match[1].length !== expectedSpaces) {
|
||||||
|
errors.push(item.lineNumber);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
10
test/spaces_after_list_marker.json
Normal file
10
test/spaces_after_list_marker.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"default": true,
|
||||||
|
"MD007": {
|
||||||
|
"indent": 4
|
||||||
|
},
|
||||||
|
"MD030": {
|
||||||
|
"ul_multi": 3,
|
||||||
|
"ol_multi": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
74
test/spaces_after_list_marker.md
Normal file
74
test/spaces_after_list_marker.md
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
Normal list
|
||||||
|
|
||||||
|
* Foo
|
||||||
|
* Bar
|
||||||
|
* Baz
|
||||||
|
|
||||||
|
List with incorrect spacing
|
||||||
|
|
||||||
|
* Foo {MD030}
|
||||||
|
* Bar {MD030}
|
||||||
|
* Baz {MD030}
|
||||||
|
|
||||||
|
List with children:
|
||||||
|
|
||||||
|
* Foo {MD030}
|
||||||
|
* Bar {MD030}
|
||||||
|
* Baz
|
||||||
|
|
||||||
|
List with children and correct spacing:
|
||||||
|
|
||||||
|
* Foo
|
||||||
|
* Bar
|
||||||
|
* Baz (This sublist has no children)
|
||||||
|
|
||||||
|
List with Multiple paragraphs and correct spacing
|
||||||
|
|
||||||
|
* Foo
|
||||||
|
|
||||||
|
Here is the second paragraph
|
||||||
|
|
||||||
|
* All items in the list need the same indent
|
||||||
|
|
||||||
|
List with multiple paragraphs and incorrect spacing
|
||||||
|
|
||||||
|
* Foo {MD030}
|
||||||
|
|
||||||
|
Here is the second paragraph
|
||||||
|
|
||||||
|
* Bar {MD030}
|
||||||
|
|
||||||
|
List with code blocks:
|
||||||
|
|
||||||
|
* Foo
|
||||||
|
|
||||||
|
Here is some code
|
||||||
|
|
||||||
|
* Bar
|
||||||
|
|
||||||
|
Ordered lists:
|
||||||
|
|
||||||
|
1. Foo
|
||||||
|
1. Bar
|
||||||
|
1. Baz
|
||||||
|
|
||||||
|
And with incorrect spacing:
|
||||||
|
|
||||||
|
1. Foo {MD030}
|
||||||
|
1. Bar {MD030}
|
||||||
|
1. Baz {MD030}
|
||||||
|
|
||||||
|
Ordered lists with children:
|
||||||
|
|
||||||
|
1. Foo {MD030}
|
||||||
|
* Hi
|
||||||
|
1. Bar {MD030}
|
||||||
|
1. Baz {MD030}
|
||||||
|
|
||||||
|
Ordered lists with children (correct spacing), and with something other than
|
||||||
|
the first item determining that the entire list has children:
|
||||||
|
|
||||||
|
1. Foo
|
||||||
|
1. Bar
|
||||||
|
* Hi
|
||||||
|
1. Baz
|
||||||
Loading…
Add table
Add a link
Reference in a new issue