diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js
index e2b21587..cf36f566 100644
--- a/demo/markdownlint-browser.js
+++ b/demo/markdownlint-browser.js
@@ -181,17 +181,6 @@ function isBlankLine(line) {
}
module.exports.isBlankLine = isBlankLine;
-/**
- * Compare function for Array.prototype.sort for ascending order of numbers.
- *
- * @param {number} a First number.
- * @param {number} b Second number.
- * @returns {number} Positive value if a>b, negative value if b} set Set of numbers.
+ * @param {number} start Starting number.
+ * @param {number} end Ending number.
+ * @returns {void}
+ */
+function addRangeToSet(set, start, end) {
+ for (let i = start; i <= end; i++) {
+ set.add(i);
+ }
+}
// eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */
@@ -3757,63 +3759,69 @@ module.exports = {
"names": [ "MD009", "no-trailing-spaces" ],
"description": "Trailing spaces",
"tags": [ "whitespace" ],
- "parser": "markdownit",
+ "parser": "micromark",
"function": function MD009(params, onError) {
let brSpaces = params.config.br_spaces;
brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces);
const listItemEmptyLines = !!params.config.list_item_empty_lines;
const strict = !!params.config.strict;
- const listItemLineNumbers = [];
- if (listItemEmptyLines) {
- filterTokens(params, "list_item_open", (token) => {
- for (let i = token.map[0]; i < token.map[1]; i++) {
- listItemLineNumbers.push(i + 1);
- }
- });
- listItemLineNumbers.sort(numericSortAscending);
+ const { tokens } = params.parsers.micromark;
+ const codeBlockLineNumbers = new Set();
+ for (const codeBlock of filterByTypes(tokens, [ "codeFenced" ])) {
+ addRangeToSet(codeBlockLineNumbers, codeBlock.startLine + 1, codeBlock.endLine - 1);
}
- const paragraphLineNumbers = [];
- const codeInlineLineNumbers = [];
+ for (const codeBlock of filterByTypes(tokens, [ "codeIndented" ])) {
+ addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
+ }
+ const listItemLineNumbers = new Set();
+ if (listItemEmptyLines) {
+ for (const listBlock of filterByTypes(tokens, [ "listOrdered", "listUnordered" ])) {
+ addRangeToSet(listItemLineNumbers, listBlock.startLine, listBlock.endLine);
+ let trailingIndent = true;
+ for (let i = listBlock.children.length - 1; i >= 0; i--) {
+ const child = listBlock.children[i];
+ switch (child.type) {
+ case "content":
+ trailingIndent = false;
+ break;
+ case "listItemIndent":
+ if (trailingIndent) {
+ listItemLineNumbers.delete(child.startLine);
+ }
+ break;
+ case "listItemPrefix":
+ trailingIndent = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ const paragraphLineNumbers = new Set();
+ const codeInlineLineNumbers = new Set();
if (strict) {
- filterTokens(params, "paragraph_open", (token) => {
- for (let i = token.map[0]; i < token.map[1] - 1; i++) {
- paragraphLineNumbers.push(i + 1);
- }
- });
- const addLineNumberRange = (start, end) => {
- for (let i = start; i < end; i++) {
- codeInlineLineNumbers.push(i);
- }
- };
- filterTokens(params, "inline", (token) => {
- let start = 0;
- for (const child of token.children) {
- if (start > 0) {
- addLineNumberRange(start, child.lineNumber);
- start = 0;
- }
- if (child.type === "code_inline") {
- start = child.lineNumber;
- }
- }
- if (start > 0) {
- addLineNumberRange(start, token.map[1]);
- }
- });
+ for (const paragraph of filterByTypes(tokens, [ "paragraph" ])) {
+ addRangeToSet(paragraphLineNumbers, paragraph.startLine, paragraph.endLine - 1);
+ }
+ for (const codeText of filterByTypes(tokens, [ "codeText" ])) {
+ addRangeToSet(codeInlineLineNumbers, codeText.startLine, codeText.endLine - 1);
+ }
}
const expected = (brSpaces < 2) ? 0 : brSpaces;
- forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
+ for (let lineIndex = 0; lineIndex < params.lines.length; lineIndex++) {
+ const line = params.lines[lineIndex];
const lineNumber = lineIndex + 1;
const trailingSpaces = line.length - line.trimEnd().length;
if (
trailingSpaces &&
- !inCode &&
- !includesSorted(listItemLineNumbers, lineNumber) &&
+ !codeBlockLineNumbers.has(lineNumber) &&
+ !listItemLineNumbers.has(lineNumber) &&
(
(expected !== trailingSpaces) ||
(strict &&
- (!includesSorted(paragraphLineNumbers, lineNumber) ||
- includesSorted(codeInlineLineNumbers, lineNumber)))
+ (!paragraphLineNumbers.has(lineNumber) ||
+ codeInlineLineNumbers.has(lineNumber)))
)
) {
const column = line.length - trailingSpaces + 1;
@@ -3827,9 +3835,10 @@ module.exports = {
{
"editColumn": column,
"deleteCount": trailingSpaces
- });
+ }
+ );
}
- });
+ }
}
};
diff --git a/helpers/helpers.js b/helpers/helpers.js
index 976b85d9..6b0c5197 100644
--- a/helpers/helpers.js
+++ b/helpers/helpers.js
@@ -169,17 +169,6 @@ function isBlankLine(line) {
}
module.exports.isBlankLine = isBlankLine;
-/**
- * Compare function for Array.prototype.sort for ascending order of numbers.
- *
- * @param {number} a First number.
- * @param {number} b Second number.
- * @returns {number} Positive value if a>b, negative value if b} set Set of numbers.
+ * @param {number} start Starting number.
+ * @param {number} end Ending number.
+ * @returns {void}
+ */
+function addRangeToSet(set, start, end) {
+ for (let i = start; i <= end; i++) {
+ set.add(i);
+ }
+}
// eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */
@@ -12,63 +25,69 @@ module.exports = {
"names": [ "MD009", "no-trailing-spaces" ],
"description": "Trailing spaces",
"tags": [ "whitespace" ],
- "parser": "markdownit",
+ "parser": "micromark",
"function": function MD009(params, onError) {
let brSpaces = params.config.br_spaces;
brSpaces = Number((brSpaces === undefined) ? 2 : brSpaces);
const listItemEmptyLines = !!params.config.list_item_empty_lines;
const strict = !!params.config.strict;
- const listItemLineNumbers = [];
- if (listItemEmptyLines) {
- filterTokens(params, "list_item_open", (token) => {
- for (let i = token.map[0]; i < token.map[1]; i++) {
- listItemLineNumbers.push(i + 1);
- }
- });
- listItemLineNumbers.sort(numericSortAscending);
+ const { tokens } = params.parsers.micromark;
+ const codeBlockLineNumbers = new Set();
+ for (const codeBlock of filterByTypes(tokens, [ "codeFenced" ])) {
+ addRangeToSet(codeBlockLineNumbers, codeBlock.startLine + 1, codeBlock.endLine - 1);
}
- const paragraphLineNumbers = [];
- const codeInlineLineNumbers = [];
+ for (const codeBlock of filterByTypes(tokens, [ "codeIndented" ])) {
+ addRangeToSet(codeBlockLineNumbers, codeBlock.startLine, codeBlock.endLine);
+ }
+ const listItemLineNumbers = new Set();
+ if (listItemEmptyLines) {
+ for (const listBlock of filterByTypes(tokens, [ "listOrdered", "listUnordered" ])) {
+ addRangeToSet(listItemLineNumbers, listBlock.startLine, listBlock.endLine);
+ let trailingIndent = true;
+ for (let i = listBlock.children.length - 1; i >= 0; i--) {
+ const child = listBlock.children[i];
+ switch (child.type) {
+ case "content":
+ trailingIndent = false;
+ break;
+ case "listItemIndent":
+ if (trailingIndent) {
+ listItemLineNumbers.delete(child.startLine);
+ }
+ break;
+ case "listItemPrefix":
+ trailingIndent = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ const paragraphLineNumbers = new Set();
+ const codeInlineLineNumbers = new Set();
if (strict) {
- filterTokens(params, "paragraph_open", (token) => {
- for (let i = token.map[0]; i < token.map[1] - 1; i++) {
- paragraphLineNumbers.push(i + 1);
- }
- });
- const addLineNumberRange = (start, end) => {
- for (let i = start; i < end; i++) {
- codeInlineLineNumbers.push(i);
- }
- };
- filterTokens(params, "inline", (token) => {
- let start = 0;
- for (const child of token.children) {
- if (start > 0) {
- addLineNumberRange(start, child.lineNumber);
- start = 0;
- }
- if (child.type === "code_inline") {
- start = child.lineNumber;
- }
- }
- if (start > 0) {
- addLineNumberRange(start, token.map[1]);
- }
- });
+ for (const paragraph of filterByTypes(tokens, [ "paragraph" ])) {
+ addRangeToSet(paragraphLineNumbers, paragraph.startLine, paragraph.endLine - 1);
+ }
+ for (const codeText of filterByTypes(tokens, [ "codeText" ])) {
+ addRangeToSet(codeInlineLineNumbers, codeText.startLine, codeText.endLine - 1);
+ }
}
const expected = (brSpaces < 2) ? 0 : brSpaces;
- forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
+ for (let lineIndex = 0; lineIndex < params.lines.length; lineIndex++) {
+ const line = params.lines[lineIndex];
const lineNumber = lineIndex + 1;
const trailingSpaces = line.length - line.trimEnd().length;
if (
trailingSpaces &&
- !inCode &&
- !includesSorted(listItemLineNumbers, lineNumber) &&
+ !codeBlockLineNumbers.has(lineNumber) &&
+ !listItemLineNumbers.has(lineNumber) &&
(
(expected !== trailingSpaces) ||
(strict &&
- (!includesSorted(paragraphLineNumbers, lineNumber) ||
- includesSorted(codeInlineLineNumbers, lineNumber)))
+ (!paragraphLineNumbers.has(lineNumber) ||
+ codeInlineLineNumbers.has(lineNumber)))
)
) {
const column = line.length - trailingSpaces + 1;
@@ -82,8 +101,9 @@ module.exports = {
{
"editColumn": column,
"deleteCount": trailingSpaces
- });
+ }
+ );
}
- });
+ }
}
};
diff --git a/test/snapshots/markdownlint-test-scenarios.js.md b/test/snapshots/markdownlint-test-scenarios.js.md
index 3beb496d..b04891f0 100644
--- a/test/snapshots/markdownlint-test-scenarios.js.md
+++ b/test/snapshots/markdownlint-test-scenarios.js.md
@@ -57272,6 +57272,25 @@ Generated by [AVA](https://avajs.dev).
'no-trailing-spaces',
],
},
+ {
+ errorContext: null,
+ errorDetail: 'Expected: 0 or 2; Actual: 6',
+ errorRange: [
+ 1,
+ 6,
+ ],
+ fixInfo: {
+ deleteCount: 6,
+ editColumn: 1,
+ },
+ lineNumber: 35,
+ ruleDescription: 'Trailing spaces',
+ ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md009.md',
+ ruleNames: [
+ 'MD009',
+ 'no-trailing-spaces',
+ ],
+ },
{
errorContext: null,
errorDetail: 'Expected: 0 or 2; Actual: 6',
@@ -57310,6 +57329,82 @@ Generated by [AVA](https://avajs.dev).
'no-trailing-spaces',
],
},
+ {
+ errorContext: null,
+ errorDetail: 'Expected: 0 or 2; Actual: 3',
+ errorRange: [
+ 1,
+ 3,
+ ],
+ fixInfo: {
+ deleteCount: 3,
+ editColumn: 1,
+ },
+ lineNumber: 57,
+ ruleDescription: 'Trailing spaces',
+ ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md009.md',
+ ruleNames: [
+ 'MD009',
+ 'no-trailing-spaces',
+ ],
+ },
+ {
+ errorContext: null,
+ errorDetail: 'Expected: 0 or 2; Actual: 3',
+ errorRange: [
+ 1,
+ 3,
+ ],
+ fixInfo: {
+ deleteCount: 3,
+ editColumn: 1,
+ },
+ lineNumber: 58,
+ ruleDescription: 'Trailing spaces',
+ ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md009.md',
+ ruleNames: [
+ 'MD009',
+ 'no-trailing-spaces',
+ ],
+ },
+ {
+ errorContext: null,
+ errorDetail: 'Expected: 0 or 2; Actual: 3',
+ errorRange: [
+ 1,
+ 3,
+ ],
+ fixInfo: {
+ deleteCount: 3,
+ editColumn: 1,
+ },
+ lineNumber: 60,
+ ruleDescription: 'Trailing spaces',
+ ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md009.md',
+ ruleNames: [
+ 'MD009',
+ 'no-trailing-spaces',
+ ],
+ },
+ {
+ errorContext: null,
+ errorDetail: 'Expected: 0 or 2; Actual: 3',
+ errorRange: [
+ 1,
+ 3,
+ ],
+ fixInfo: {
+ deleteCount: 3,
+ editColumn: 1,
+ },
+ lineNumber: 61,
+ ruleDescription: 'Trailing spaces',
+ ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md009.md',
+ ruleNames: [
+ 'MD009',
+ 'no-trailing-spaces',
+ ],
+ },
],
fixed: `# Heading␊
␊
@@ -57345,7 +57440,7 @@ Generated by [AVA](https://avajs.dev).
␊
1. text␊
text␊
- ␊
+ ␊
1. text␊
␊
1. text␊
@@ -57361,10 +57456,24 @@ Generated by [AVA](https://avajs.dev).
- text␊
text␊
␊
+ {MD009:35}␊
{MD009:37}␊
{MD009:50}␊
␊
+ 1. text␊
+ text␊
+ ␊
+ ␊
+ 1. text␊
+ ␊
+ ␊
+ {MD009:57}␊
+ {MD009:58}␊
+ {MD009:60}␊
+ {MD009:61}␊
+ ␊