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}␊ + ␊