diff --git a/lib/md037.js b/lib/md037.js index d4112b8c..14390837 100644 --- a/lib/md037.js +++ b/lib/md037.js @@ -2,7 +2,7 @@ "use strict"; -const { addErrorContext, forEachLine } = require("../helpers"); +const { addErrorContext, forEachLine, isBlankLine } = require("../helpers"); const { lineMetadata } = require("./cache"); const emphasisRe = /(^|[^\\])(?:(\*\*?\*?)|(__?_?))/g; @@ -15,21 +15,72 @@ module.exports = { "description": "Spaces inside emphasis markers", "tags": [ "whitespace", "emphasis" ], "function": function MD037(params, onError) { + // eslint-disable-next-line init-declarations + let effectiveEmphasisLength, emphasisIndex, emphasisLength, pendingError; + // eslint-disable-next-line jsdoc/require-jsdoc + function resetRunTracking() { + emphasisIndex = -1; + emphasisLength = 0; + effectiveEmphasisLength = 0; + pendingError = null; + } + // eslint-disable-next-line jsdoc/require-jsdoc + function handleRunEnd(line, lineIndex, contextLength, match, matchIndex) { + // Close current run + let content = line.substring(emphasisIndex, matchIndex); + if (!emphasisLength) { + content = content.trimStart(); + } + if (!match) { + content = content.trimEnd(); + } + const leftSpace = leftSpaceRe.test(content); + const rightSpace = rightSpaceRe.test(content); + if (leftSpace || rightSpace) { + // Report the violation + const contextStart = emphasisIndex - emphasisLength; + const contextEnd = matchIndex + contextLength; + const context = line.substring(contextStart, contextEnd); + const column = contextStart + 1; + const length = contextEnd - contextStart; + const leftMarker = line.substring(contextStart, emphasisIndex); + const rightMarker = match ? (match[2] || match[3]) : ""; + const fixedText = `${leftMarker}${content.trim()}${rightMarker}`; + return [ + onError, + lineIndex + 1, + context, + leftSpace, + rightSpace, + [ column, length ], + { + "editColumn": column, + "deleteCount": length, + "insertText": fixedText + } + ]; + } + return null; + } + // Initialize + resetRunTracking(); forEachLine( lineMetadata(), - (line, lineIndex, inCode, onFence, inTable, inItem, inBreak) => { - if (inCode || inBreak) { + (line, lineIndex, inCode, onFence, inTable, inItem, onBreak) => { + const onItemStart = (inItem === 1); + if (inCode || inTable || onBreak || onItemStart || isBlankLine(line)) { + // Emphasis resets when leaving a block + resetRunTracking(); + } + if (inCode || onBreak) { // Emphasis has no meaning here return; } - if (inItem === 1) { + if (onItemStart) { // Trim overlapping '*' list item marker line = line.replace(asteriskListItemMarkerRe, "$1 $2"); } let match = null; - let emphasisIndex = -1; - let emphasisLength = 0; - let effectiveEmphasisLength = 0; // Match all emphasis-looking runs in the line... while ((match = emphasisRe.exec(line))) { const matchIndex = match.index + match[1].length; @@ -40,38 +91,18 @@ module.exports = { emphasisLength = matchLength; effectiveEmphasisLength = matchLength; } else if (matchLength === effectiveEmphasisLength) { - // Close current run - const content = line.substring(emphasisIndex, matchIndex); - const leftSpace = leftSpaceRe.test(content); - const rightSpace = rightSpaceRe.test(content); - if (leftSpace || rightSpace) { - // Report the violation - const contextStart = emphasisIndex - emphasisLength; - const contextEnd = matchIndex + effectiveEmphasisLength; - const context = line.substring(contextStart, contextEnd); - const column = contextStart + 1; - const length = contextEnd - contextStart; - const leftMarker = line.substring(contextStart, emphasisIndex); - const rightMarker = match[2] || match[3]; - const fixedText = `${leftMarker}${content.trim()}${rightMarker}`; - addErrorContext( - onError, - lineIndex + 1, - context, - leftSpace, - rightSpace, - [ column, length ], - { - "editColumn": column, - "deleteCount": length, - "insertText": fixedText - } - ); + // Ending an existing run, report any pending error + if (pendingError) { + addErrorContext(...pendingError); + pendingError = null; + } + const error = handleRunEnd( + line, lineIndex, effectiveEmphasisLength, match, matchIndex); + if (error) { + addErrorContext(...error); } // Reset - emphasisIndex = -1; - emphasisLength = 0; - effectiveEmphasisLength = 0; + resetRunTracking(); } else if (matchLength === 3) { // Swap internal run length (1->2 or 2->1) effectiveEmphasisLength = matchLength - effectiveEmphasisLength; @@ -83,6 +114,13 @@ module.exports = { effectiveEmphasisLength += matchLength; } } + if (emphasisIndex !== -1) { + pendingError = pendingError || + handleRunEnd(line, lineIndex, 0, null, line.length); + // Adjust for pending run on new line + emphasisIndex = 0; + emphasisLength = 0; + } } ); } diff --git a/test/detailed-results-MD031-MD040.md b/test/detailed-results-MD031-MD040.md index 47f11d5c..6dc52a18 100644 --- a/test/detailed-results-MD031-MD040.md +++ b/test/detailed-results-MD031-MD040.md @@ -58,3 +58,6 @@ span```` text text. Text [ space](link) text [space ](link) text [ space ](link) text. + +Space * inside +multi-line * emphasis. diff --git a/test/detailed-results-MD031-MD040.md.fixed b/test/detailed-results-MD031-MD040.md.fixed index 70e70654..452d77b9 100644 --- a/test/detailed-results-MD031-MD040.md.fixed +++ b/test/detailed-results-MD031-MD040.md.fixed @@ -60,3 +60,6 @@ span```` text text. Text [space](link) text [space](link) text [space](link) text. + +Space *inside +multi-line* emphasis. diff --git a/test/detailed-results-MD031-MD040.results.json b/test/detailed-results-MD031-MD040.results.json index 059d825a..307040f3 100644 --- a/test/detailed-results-MD031-MD040.results.json +++ b/test/detailed-results-MD031-MD040.results.json @@ -116,6 +116,24 @@ "errorContext": "__ some __", "errorRange": [ 19, 10 ] }, + { + "lineNumber": 62, + "ruleNames": [ "MD037", "no-space-in-emphasis" ], + "ruleDescription": "Spaces inside emphasis markers", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md037", + "errorDetail": null, + "errorContext": "* inside", + "errorRange": [ 7, 8 ] + }, + { + "lineNumber": 63, + "ruleNames": [ "MD037", "no-space-in-emphasis" ], + "ruleDescription": "Spaces inside emphasis markers", + "ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md037", + "errorDetail": null, + "errorContext": "multi-line *", + "errorRange": [ 1, 12 ] + }, { "lineNumber": 17, "ruleNames": [ "MD038", "no-space-in-code" ], diff --git a/test/spaces-inside-emphasis-markers-multiple-lines.md b/test/spaces-inside-emphasis-markers-multiple-lines.md new file mode 100644 index 00000000..76ad38ec --- /dev/null +++ b/test/spaces-inside-emphasis-markers-multiple-lines.md @@ -0,0 +1,75 @@ +# Space Inside Emphasis Markers, Multiple Lines + +Text *emphasis +emphasis* text + +Text *emphasis* *emphasis +emphasis* *emphasis* text + +Text *emphasis* text *emphasis +emphasis* text *emphasis* text + +Text *emphasis* *emphasis +emphasis* *emphasis* *emphasis +emphasis* text *emphasis +emphasis* text *emphasis* text + +Text text +text *emphasis +emphasis emphasis +emphasis* text +text text + +Text * asterisk + +Text * asterisk + +* Item *emphasis* item +* Item *emphasis* item +* Item *emphasis + emphasis* item +* Item *emphasis* item + +* Item * asterisk +* Item * asterisk + +Text * emphasis {MD037} +emphasis* text + +Text *emphasis +emphasis * text {MD037} + +Text * emphasis {MD037} +emphasis * text {MD037} + +Text *emphasis * *emphasis {MD037} +emphasis* * emphasis* text {MD037} + +Text *emphasis* * emphasis {MD037} +emphasis * *emphasis* text {MD037} + +Text * emphasis * * emphasis {MD037} +emphasis * * emphasis * text {MD037} + +Text text +text * emphasis {MD037} +emphasis emphasis +emphasis * text {MD037} +text text + +* Item *emphasis* item +* Item * emphasis {MD037} + emphasis* item +* Item *emphasis + emphasis * item {MD037} +* Item * emphasis {MD037} + emphasis * item {MD037} +* Item *emphasis* item +* Item item item + item * emphasis * item {MD037} + +Text _ emphasis {MD037} +emphasis _ text {MD037} + +Text ** bold {MD037} +bold ** text {MD037}