Update MD009/no-trailing-spaces to include strict mode (fixes #216).

This commit is contained in:
David Anson 2019-12-09 22:05:57 -08:00
parent a9251c533f
commit 6f3c67f760
10 changed files with 193 additions and 11 deletions

View file

@ -318,7 +318,7 @@ Tags: whitespace
Aliases: no-trailing-spaces Aliases: no-trailing-spaces
Parameters: br_spaces, list_item_empty_lines (number; default 2, boolean; default false) Parameters: br_spaces, list_item_empty_lines, strict (number; default 2, boolean; default false, boolean; default false)
This rule is triggered on any lines that end with unexpected whitespace. To fix this, This rule is triggered on any lines that end with unexpected whitespace. To fix this,
remove the trailing space from the end of the line. remove the trailing space from the end of the line.
@ -330,9 +330,18 @@ value allows 2 spaces to indicate a hard break (\<br> element).
Note: You must set `br_spaces` to a value >= 2 for this parameter to take effect. Note: You must set `br_spaces` to a value >= 2 for this parameter to take effect.
Setting `br_spaces` to 1 behaves the same as 0, disallowing any trailing spaces. Setting `br_spaces` to 1 behaves the same as 0, disallowing any trailing spaces.
By default, this rule will not trigger when the allowed number of spaces is used,
even when it doesn't create a hard break (for example, at the end of a paragraph).
To report such instances as well, set the `strict` parameter to `true`.
```markdown
Text text text
text[2 spaces]
```
Using spaces to indent blank lines inside a list item is usually not necessary, Using spaces to indent blank lines inside a list item is usually not necessary,
but some parsers require it. Set the `list_item_empty_lines` parameter to `true` but some parsers require it. Set the `list_item_empty_lines` parameter to `true`
to allow this: to allow this (even when `strict` is `true`):
```markdown ```markdown
- list item text - list item text

View file

@ -2,10 +2,14 @@
"use strict"; "use strict";
const { addError, filterTokens, forEachLine, includesSorted } = const { addError, filterTokens, forEachInlineCodeSpan, forEachLine,
require("../helpers"); includesSorted, newLineRe } = require("../helpers");
const { lineMetadata } = require("./cache"); const { lineMetadata } = require("./cache");
function numericSortAscending(a, b) {
return a - b;
}
module.exports = { module.exports = {
"names": [ "MD009", "no-trailing-spaces" ], "names": [ "MD009", "no-trailing-spaces" ],
"description": "Trailing spaces", "description": "Trailing spaces",
@ -15,17 +19,38 @@ module.exports = {
if (brSpaces === undefined) { if (brSpaces === undefined) {
brSpaces = 2; brSpaces = 2;
} }
const listItemEmptyLines = params.config.list_item_empty_lines; const listItemEmptyLines = !!params.config.list_item_empty_lines;
const allowListItemEmptyLines = const strict = !!params.config.strict;
(listItemEmptyLines === undefined) ? false : !!listItemEmptyLines;
const listItemLineNumbers = []; const listItemLineNumbers = [];
if (allowListItemEmptyLines) { if (listItemEmptyLines) {
filterTokens(params, "list_item_open", (token) => { filterTokens(params, "list_item_open", (token) => {
for (let i = token.map[0]; i < token.map[1]; i++) { for (let i = token.map[0]; i < token.map[1]; i++) {
listItemLineNumbers.push(i + 1); listItemLineNumbers.push(i + 1);
} }
}); });
listItemLineNumbers.sort((a, b) => a - b); listItemLineNumbers.sort(numericSortAscending);
}
const paragraphLineNumbers = [];
const codeInlineLineNumbers = [];
if (strict) {
filterTokens(params, "paragraph_open", (token) => {
for (let i = token.map[0]; i < token.map[1] - 1; i++) {
paragraphLineNumbers.push(i + 1);
}
});
paragraphLineNumbers.sort(numericSortAscending);
filterTokens(params, "inline", (token) => {
if (token.children.some((child) => child.type === "code_inline")) {
const tokenLines = params.lines.slice(token.map[0], token.map[1]);
forEachInlineCodeSpan(tokenLines.join("\n"), (code, lineIndex) => {
const codeLineCount = code.split(newLineRe).length;
for (let i = 0; i < codeLineCount; i++) {
codeInlineLineNumbers.push(token.lineNumber + lineIndex + i);
}
});
}
});
codeInlineLineNumbers.sort(numericSortAscending);
} }
const expected = (brSpaces < 2) ? 0 : brSpaces; const expected = (brSpaces < 2) ? 0 : brSpaces;
let inFencedCode = 0; let inFencedCode = 0;
@ -35,7 +60,10 @@ module.exports = {
const trailingSpaces = line.length - line.trimRight().length; const trailingSpaces = line.length - line.trimRight().length;
if ((!inCode || inFencedCode) && trailingSpaces && if ((!inCode || inFencedCode) && trailingSpaces &&
!includesSorted(listItemLineNumbers, lineNumber)) { !includesSorted(listItemLineNumbers, lineNumber)) {
if (expected !== trailingSpaces) { if ((expected !== trailingSpaces) ||
(strict &&
(!includesSorted(paragraphLineNumbers, lineNumber) ||
includesSorted(codeInlineLineNumbers, lineNumber)))) {
const column = line.length - trailingSpaces + 1; const column = line.length - trailingSpaces + 1;
addError( addError(
onError, onError,

View file

@ -15,7 +15,6 @@ module.exports = {
if (token.type === "paragraph_open") { if (token.type === "paragraph_open") {
return function inParagraph(t) { return function inParagraph(t) {
// Always paragraph_open/inline/paragraph_close, // Always paragraph_open/inline/paragraph_close,
// omit (t.type === "inline")
const children = t.children.filter(function notEmptyText(child) { const children = t.children.filter(function notEmptyText(child) {
return (child.type !== "text") || (child.content !== ""); return (child.type !== "text") || (child.content !== "");
}); });

View file

@ -109,6 +109,11 @@ rules.forEach(function forRule(rule) {
"description": "Allow spaces for empty lines in list items", "description": "Allow spaces for empty lines in list items",
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"strict": {
"description": "Include unnecessary breaks",
"type": "boolean",
"default": false
} }
}; };
break; break;

View file

@ -267,6 +267,11 @@
"description": "Allow spaces for empty lines in list items", "description": "Allow spaces for empty lines in list items",
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"strict": {
"description": "Include unnecessary breaks",
"type": "boolean",
"default": false
} }
}, },
"additionalProperties": false "additionalProperties": false
@ -288,6 +293,11 @@
"description": "Allow spaces for empty lines in list items", "description": "Allow spaces for empty lines in list items",
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"strict": {
"description": "Include unnecessary breaks",
"type": "boolean",
"default": false
} }
}, },
"additionalProperties": false "additionalProperties": false

View file

@ -0,0 +1,6 @@
{
"default": true,
"MD009": {
"strict": true
}
}

64
test/hard-line-breaks.md Normal file
View file

@ -0,0 +1,64 @@
# Hard Line Breaks
hard
break
hard\
break
hard
break
hard
break
hard\
break
*hard
break*
*hard\
break*
`code
span`
`code\
span`
not\
not
## not\
### not
- Item
- Item
- Item
- Item
Text text
text `code
span code
span` text
text
Text text
text text
text
{MD009:9}
{MD009:24}
{MD009:32}
{MD009:36}
{MD009:39}
{MD009:41}
{MD009:43}
{MD009:48}
{MD009:54}

View file

@ -1553,6 +1553,7 @@ module.exports.doc = function doc(test) {
ruleUsesParams = ruleUsesParams.map(function forUse(use) { ruleUsesParams = ruleUsesParams.map(function forUse(use) {
return use.split(".").pop(); return use.split(".").pop();
}); });
ruleUsesParams.sort();
} }
} else if (/^Tags: /.test(token.content) && rule) { } else if (/^Tags: /.test(token.content) && rule) {
test.deepEqual(token.content.split(tagAliasParameterRe).slice(1), test.deepEqual(token.content.split(tagAliasParameterRe).slice(1),
@ -1571,6 +1572,7 @@ module.exports.doc = function doc(test) {
inDetails = inDetails || (part[0] === "("); inDetails = inDetails || (part[0] === "(");
return !inDetails; return !inDetails;
}); });
parameters.sort();
test.deepEqual(parameters, ruleUsesParams, test.deepEqual(parameters, ruleUsesParams,
"Missing parameter for rule " + rule.names); "Missing parameter for rule " + rule.names);
ruleUsesParams = null; ruleUsesParams = null;

View file

@ -0,0 +1,7 @@
{
"default": true,
"MD009": {
"list_item_empty_lines": true,
"strict": true
}
}

View file

@ -0,0 +1,52 @@
# Heading
1. text
text
1. text
text
1. text
text
1. text
text
1. text
text
1. text
{MD009:16}
{MD009:18}
1. text
text
1. text
text
1. text
text
1. text
text
1. text
text
1. text
1. text
- text
text
- text
text
- text
text
- text
text
{MD009:37}
{MD009:50}