From 291597edb924c0f6a0d722d6877915b9f6dd3013 Mon Sep 17 00:00:00 2001 From: David Anson Date: Sun, 28 Nov 2021 23:18:57 -0800 Subject: [PATCH] Update rules MD049/emphasis-style and MD050/strong-style to include range and fixInfo when reporting issues (i.e., to be automatically fixable). --- demo/markdownlint-browser.js | 85 +++++++++++++++++-- doc/Rules.md | 4 + helpers/helpers.js | 55 ++++++++++++ lib/md049.js | 35 ++++++-- lib/md050.js | 35 ++++++-- test/break-all-the-rules.md | 2 +- test/detailed-results-MD041-MD050.md | 4 +- test/detailed-results-MD041-MD050.md.fixed | 4 +- .../detailed-results-MD041-MD050.results.json | 62 +++++++++++++- test/fix_102_extra_nodes_in_link_text.md | 10 ++- test/links-with-markup.md | 2 +- test/mixed-emphasis-markers.md | 2 + test/proper-names.md | 4 +- 13 files changed, 263 insertions(+), 41 deletions(-) diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 81406185..2296d47f 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -783,6 +783,57 @@ module.exports.applyFixes = function applyFixes(input, errors) { // Return corrected input return lines.filter(function (line) { return line !== null; }).join(lineEnding); }; +/** + * Gets the range and fixInfo values for reporting an error if the expected + * text is found on the specified line. + * + * @param {string[]} lines Lines of Markdown content. + * @param {number} lineIndex Line index to check. + * @param {string} search Text to search for. + * @param {string} replace Text to replace with. + * @returns {Object} Range and fixInfo wrapper. + */ +function getRangeAndFixInfoIfFound(lines, lineIndex, search, replace) { + var range = null; + var fixInfo = null; + var searchIndex = lines[lineIndex].indexOf(search); + if (searchIndex !== -1) { + var column = searchIndex + 1; + var length = search.length; + range = [column, length]; + fixInfo = { + "editColumn": column, + "deleteCount": length, + "insertText": replace + }; + } + return { + range: range, + fixInfo: fixInfo + }; +} +module.exports.getRangeAndFixInfoIfFound = getRangeAndFixInfoIfFound; +/** + * Gets the next (subsequent) child token if it is of the expected type. + * + * @param {Object} parentToken Parent token. + * @param {Object} childToken Child token basis. + * @param {string} nextType Token type of next token. + * @param {string} nextNextType Token type of next-next token. + * @returns {Object} Next token. + */ +function getNextChildToken(parentToken, childToken, nextType, nextNextType) { + var children = parentToken.children; + var index = children.indexOf(childToken); + if ((index !== -1) && + (children.length > index + 2) && + (children[index + 1].type === nextType) && + (children[index + 2].type === nextNextType)) { + return children[index + 1]; + } + return null; +} +module.exports.getNextChildToken = getNextChildToken; /***/ }), @@ -4083,20 +4134,31 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild, getNextChildToken = _a.getNextChildToken, getRangeAndFixInfoIfFound = _a.getRangeAndFixInfoIfFound; module.exports = { "names": ["MD049", "emphasis-style"], "description": "Emphasis style should be consistent", "tags": ["emphasis"], "function": function MD049(params, onError) { var expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "em_open", function (token) { + forEachInlineChild(params, "em_open", function (token, parent) { var lineNumber = token.lineNumber, markup = token.markup; var markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf(onError, lineNumber, expectedStyle, markupStyle); + if (expectedStyle !== markupStyle) { + var rangeAndFixInfo = {}; + var contentToken = getNextChildToken(parent, token, "text", "em_close"); + if (contentToken) { + var content = contentToken.content; + var actual = "" + markup + content + markup; + var expectedMarkup = (expectedStyle === "asterisk") ? "*" : "_"; + var expected = "" + expectedMarkup + content + expectedMarkup; + rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected); + } + addError(onError, lineNumber, "Expected: " + expectedStyle + "; Actual: " + markupStyle, null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); + } }); } }; @@ -4113,20 +4175,31 @@ module.exports = { "use strict"; // @ts-check -var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addErrorDetailIf = _a.addErrorDetailIf, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild; +var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, emphasisOrStrongStyleFor = _a.emphasisOrStrongStyleFor, forEachInlineChild = _a.forEachInlineChild, getNextChildToken = _a.getNextChildToken, getRangeAndFixInfoIfFound = _a.getRangeAndFixInfoIfFound; module.exports = { "names": ["MD050", "strong-style"], "description": "Strong style should be consistent", "tags": ["emphasis"], "function": function MD050(params, onError) { var expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "strong_open", function (token) { + forEachInlineChild(params, "strong_open", function (token, parent) { var lineNumber = token.lineNumber, markup = token.markup; var markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf(onError, lineNumber, expectedStyle, markupStyle); + if (expectedStyle !== markupStyle) { + var rangeAndFixInfo = {}; + var contentToken = getNextChildToken(parent, token, "text", "strong_close"); + if (contentToken) { + var content = contentToken.content; + var actual = "" + markup + content + markup; + var expectedMarkup = (expectedStyle === "asterisk") ? "**" : "__"; + var expected = "" + expectedMarkup + content + expectedMarkup; + rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected); + } + addError(onError, lineNumber, "Expected: " + expectedStyle + "; Actual: " + markupStyle, null, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo); + } }); } }; diff --git a/doc/Rules.md b/doc/Rules.md index ed0a93fa..bb3d6844 100644 --- a/doc/Rules.md +++ b/doc/Rules.md @@ -1923,6 +1923,8 @@ Aliases: emphasis-style Parameters: style ("consistent", "asterisk", "underscore"; default "consistent") +Fixable: Most violations can be fixed by tooling + This rule is triggered when the symbols used in the document for emphasis do not match the configured emphasis style: @@ -1953,6 +1955,8 @@ Aliases: strong-style Parameters: style ("consistent", "asterisk", "underscore"; default "consistent") +Fixable: Most violations can be fixed by tooling + This rule is triggered when the symbols used in the document for strong do not match the configured strong style: diff --git a/helpers/helpers.js b/helpers/helpers.js index 6e2dd1f4..c4927512 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -813,3 +813,58 @@ module.exports.applyFixes = function applyFixes(input, errors) { // Return corrected input return lines.filter((line) => line !== null).join(lineEnding); }; + +/** + * Gets the range and fixInfo values for reporting an error if the expected + * text is found on the specified line. + * + * @param {string[]} lines Lines of Markdown content. + * @param {number} lineIndex Line index to check. + * @param {string} search Text to search for. + * @param {string} replace Text to replace with. + * @returns {Object} Range and fixInfo wrapper. + */ +function getRangeAndFixInfoIfFound(lines, lineIndex, search, replace) { + let range = null; + let fixInfo = null; + const searchIndex = lines[lineIndex].indexOf(search); + if (searchIndex !== -1) { + const column = searchIndex + 1; + const length = search.length; + range = [ column, length ]; + fixInfo = { + "editColumn": column, + "deleteCount": length, + "insertText": replace + }; + } + return { + range, + fixInfo + }; +} +module.exports.getRangeAndFixInfoIfFound = getRangeAndFixInfoIfFound; + +/** + * Gets the next (subsequent) child token if it is of the expected type. + * + * @param {Object} parentToken Parent token. + * @param {Object} childToken Child token basis. + * @param {string} nextType Token type of next token. + * @param {string} nextNextType Token type of next-next token. + * @returns {Object} Next token. + */ +function getNextChildToken(parentToken, childToken, nextType, nextNextType) { + const { children } = parentToken; + const index = children.indexOf(childToken); + if ( + (index !== -1) && + (children.length > index + 2) && + (children[index + 1].type === nextType) && + (children[index + 2].type === nextNextType) + ) { + return children[index + 1]; + } + return null; +} +module.exports.getNextChildToken = getNextChildToken; diff --git a/lib/md049.js b/lib/md049.js index 0a638578..4d36df55 100644 --- a/lib/md049.js +++ b/lib/md049.js @@ -2,8 +2,8 @@ "use strict"; -const { addErrorDetailIf, emphasisOrStrongStyleFor, forEachInlineChild } = - require("../helpers"); +const { addError, emphasisOrStrongStyleFor, forEachInlineChild, + getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers"); module.exports = { "names": [ "MD049", "emphasis-style" ], @@ -11,18 +11,35 @@ module.exports = { "tags": [ "emphasis" ], "function": function MD049(params, onError) { let expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "em_open", (token) => { + forEachInlineChild(params, "em_open", (token, parent) => { const { lineNumber, markup } = token; const markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf( - onError, - lineNumber, - expectedStyle, - markupStyle - ); + if (expectedStyle !== markupStyle) { + let rangeAndFixInfo = {}; + const contentToken = getNextChildToken( + parent, token, "text", "em_close" + ); + if (contentToken) { + const { content } = contentToken; + const actual = `${markup}${content}${markup}`; + const expectedMarkup = (expectedStyle === "asterisk") ? "*" : "_"; + const expected = `${expectedMarkup}${content}${expectedMarkup}`; + rangeAndFixInfo = getRangeAndFixInfoIfFound( + params.lines, lineNumber - 1, actual, expected + ); + } + addError( + onError, + lineNumber, + `Expected: ${expectedStyle}; Actual: ${markupStyle}`, + null, + rangeAndFixInfo.range, + rangeAndFixInfo.fixInfo + ); + } }); } }; diff --git a/lib/md050.js b/lib/md050.js index 79ae8a18..2ca9260e 100644 --- a/lib/md050.js +++ b/lib/md050.js @@ -2,8 +2,8 @@ "use strict"; -const { addErrorDetailIf, emphasisOrStrongStyleFor, forEachInlineChild } = - require("../helpers"); +const { addError, emphasisOrStrongStyleFor, forEachInlineChild, + getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers"); module.exports = { "names": [ "MD050", "strong-style" ], @@ -11,18 +11,35 @@ module.exports = { "tags": [ "emphasis" ], "function": function MD050(params, onError) { let expectedStyle = String(params.config.style || "consistent"); - forEachInlineChild(params, "strong_open", (token) => { + forEachInlineChild(params, "strong_open", (token, parent) => { const { lineNumber, markup } = token; const markupStyle = emphasisOrStrongStyleFor(markup); if (expectedStyle === "consistent") { expectedStyle = markupStyle; } - addErrorDetailIf( - onError, - lineNumber, - expectedStyle, - markupStyle - ); + if (expectedStyle !== markupStyle) { + let rangeAndFixInfo = {}; + const contentToken = getNextChildToken( + parent, token, "text", "strong_close" + ); + if (contentToken) { + const { content } = contentToken; + const actual = `${markup}${content}${markup}`; + const expectedMarkup = (expectedStyle === "asterisk") ? "**" : "__"; + const expected = `${expectedMarkup}${content}${expectedMarkup}`; + rangeAndFixInfo = getRangeAndFixInfoIfFound( + params.lines, lineNumber - 1, actual, expected + ); + } + addError( + onError, + lineNumber, + `Expected: ${expectedStyle}; Actual: ${markupStyle}`, + null, + rangeAndFixInfo.range, + rangeAndFixInfo.fixInfo + ); + } }); } }; diff --git a/test/break-all-the-rules.md b/test/break-all-the-rules.md index e1744f37..140d87cd 100644 --- a/test/break-all-the-rules.md +++ b/test/break-all-the-rules.md @@ -64,7 +64,7 @@ https://example.com/page {MD034} _Section {MD036} Heading_ -Emphasis *with * space {MD037} +Emphasis _with _ space {MD037} Code `with ` space {MD038} diff --git a/test/detailed-results-MD041-MD050.md b/test/detailed-results-MD041-MD050.md index 6066a9b5..d4b6fd0b 100644 --- a/test/detailed-results-MD041-MD050.md +++ b/test/detailed-results-MD041-MD050.md @@ -28,9 +28,9 @@ Fenced code Fenced code ~~~ -Mixed *emphasis* on _this_ line +Mixed *emphasis* on _this_ line *with* multiple _issues_ -Mixed __strong emphasis__ on **this** line +Mixed __strong emphasis__ on **this** line __with__ multiple **issues** Inconsistent emphasis _text diff --git a/test/detailed-results-MD041-MD050.md.fixed b/test/detailed-results-MD041-MD050.md.fixed index f9af863a..a26adc9b 100644 --- a/test/detailed-results-MD041-MD050.md.fixed +++ b/test/detailed-results-MD041-MD050.md.fixed @@ -28,9 +28,9 @@ Fenced code Fenced code ~~~ -Mixed *emphasis* on _this_ line +Mixed *emphasis* on *this* line *with* multiple *issues* -Mixed __strong emphasis__ on **this** line +Mixed __strong emphasis__ on __this__ line __with__ multiple __issues__ Inconsistent emphasis _text diff --git a/test/detailed-results-MD041-MD050.results.json b/test/detailed-results-MD041-MD050.results.json index 346e2eb5..d9b7b38b 100644 --- a/test/detailed-results-MD041-MD050.results.json +++ b/test/detailed-results-MD041-MD050.results.json @@ -212,8 +212,35 @@ { "errorContext": null, "errorDetail": "Expected: asterisk; Actual: underscore", - "errorRange": null, - "fixInfo": null, + "errorRange": [ + 21, + 6 + ], + "fixInfo": { + "deleteCount": 6, + "editColumn": 21, + "insertText": "*this*" + }, + "lineNumber": 31, + "ruleDescription": "Emphasis style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049", + "ruleNames": [ + "MD049", + "emphasis-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: asterisk; Actual: underscore", + "errorRange": [ + 49, + 8 + ], + "fixInfo": { + "deleteCount": 8, + "editColumn": 49, + "insertText": "*issues*" + }, "lineNumber": 31, "ruleDescription": "Emphasis style should be consistent", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md049", @@ -238,8 +265,35 @@ { "errorContext": null, "errorDetail": "Expected: underscore; Actual: asterisk", - "errorRange": null, - "fixInfo": null, + "errorRange": [ + 30, + 8 + ], + "fixInfo": { + "deleteCount": 8, + "editColumn": 30, + "insertText": "__this__" + }, + "lineNumber": 33, + "ruleDescription": "Strong style should be consistent", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050", + "ruleNames": [ + "MD050", + "strong-style" + ] + }, + { + "errorContext": null, + "errorDetail": "Expected: underscore; Actual: asterisk", + "errorRange": [ + 62, + 10 + ], + "fixInfo": { + "deleteCount": 10, + "editColumn": 62, + "insertText": "__issues__" + }, "lineNumber": 33, "ruleDescription": "Strong style should be consistent", "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md050", diff --git a/test/fix_102_extra_nodes_in_link_text.md b/test/fix_102_extra_nodes_in_link_text.md index 68db9d71..133386d8 100644 --- a/test/fix_102_extra_nodes_in_link_text.md +++ b/test/fix_102_extra_nodes_in_link_text.md @@ -2,9 +2,11 @@ [test _test_ test](www.test.com) [test `test` test](www.test.com) -[test *test* test](www.test.com) {MD049} -[test *test* *test* test](www.test.com) {MD049} -[test *test* *test* *test* test](www.test.com) {MD049} +[test *test* test](www.test.com) +[test *test* *test* test](www.test.com) +[test *test* *test* *test* test](www.test.com) [test **test** test](www.test.com) -[test __test__ test](www.test.com) {MD050} +[test __test__ test](www.test.com) [this should not raise](www.shouldnotraise.com) + + diff --git a/test/links-with-markup.md b/test/links-with-markup.md index 454f3d65..9934bda2 100644 --- a/test/links-with-markup.md +++ b/test/links-with-markup.md @@ -10,7 +10,7 @@ [This link has `code` and right space ](link) {MD039} -[ This link has _emphasis_ and left space](link) {MD039} {MD049} +[ This link has *emphasis* and left space](link) {MD039} [This](link) line has [multiple](link) links. diff --git a/test/mixed-emphasis-markers.md b/test/mixed-emphasis-markers.md index e74a7ee0..92c22393 100644 --- a/test/mixed-emphasis-markers.md +++ b/test/mixed-emphasis-markers.md @@ -15,3 +15,5 @@ This paragraph _nests both *kinds* of emphasis_ marker. {MD049} This paragraph _nests both **kinds** of emphasis_ marker. {MD049} {MD050} This paragraph __nests both **kinds** of emphasis__ marker. {MD050} + + diff --git a/test/proper-names.md b/test/proper-names.md index d69b6621..c9e9bc6d 100644 --- a/test/proper-names.md +++ b/test/proper-names.md @@ -6,8 +6,6 @@ Quoted "Markdownlint" {MD044} Emphasized *Markdownlint* {MD044} -Emphasized _Markdownlint_ {MD044} {MD049} - JavaScript is a language JavaScript is not Java @@ -52,7 +50,7 @@ HTML javascript {MD033} {MD044} node.js is runtime {MD044} ```js -javascript is code {MD044} {MD046:54} +javascript is code {MD044} {MD046:52} node.js is runtime {MD044} ```