Refactor MD038/no-space-in-code to produce smaller/simpler edits and address an additional scenario.

This commit is contained in:
David Anson 2025-02-07 20:03:30 -08:00
parent 656254e64f
commit b23fc96ab2
8 changed files with 699 additions and 610 deletions

View file

@ -1,38 +1,44 @@
This rule is triggered for code spans that have content with spaces next to the This rule is triggered for code spans containing content with unnecessary space
beginning or ending backticks: next to the beginning or ending backticks:
```markdown ```markdown
`some text ` `some text `
` some text` ` some text`
` some text `
``` ```
To fix this, remove any spaces at the beginning or ending: To fix this, remove the extra space characters from the beginning and ending:
```markdown ```markdown
`some text` `some text`
``` ```
Note: Code spans containing only spaces are allowed by the specification: Note: A single leading *and* trailing space is allowed by the specification and
trimmed by the parser to support code spans that begin or end with a backtick:
```markdown
` ` or ` `
```
Note: A single leading and trailing space is allowed by the specification and
automatically trimmed by the parser (in order to allow for code spans that embed
backticks):
```markdown ```markdown
`` `backticks` `` `` `backticks` ``
`` backtick` ``
``` ```
Note: A single leading or trailing space is allowed if used to separate code Note: When single-space padding is present in the input, it will be preserved
span markers from an embedded backtick (though the space is not trimmed): (even if unnecessary):
```markdown ```markdown
`` ` embedded backtick`` ` code `
``` ```
Rationale: Violations of this rule are usually unintentional and may lead to Note: Code spans containing only spaces are allowed by the specification and are
also preserved:
```markdown
` `
` `
```
Rationale: Violations of this rule are usually unintentional and can lead to
improperly-rendered content. improperly-rendered content.

View file

@ -1528,43 +1528,49 @@ Aliases: `no-space-in-code`
Fixable: Some violations can be fixed by tooling Fixable: Some violations can be fixed by tooling
This rule is triggered for code spans that have content with spaces next to the This rule is triggered for code spans containing content with unnecessary space
beginning or ending backticks: next to the beginning or ending backticks:
```markdown ```markdown
`some text ` `some text `
` some text` ` some text`
` some text `
``` ```
To fix this, remove any spaces at the beginning or ending: To fix this, remove the extra space characters from the beginning and ending:
```markdown ```markdown
`some text` `some text`
``` ```
Note: Code spans containing only spaces are allowed by the specification: Note: A single leading *and* trailing space is allowed by the specification and
trimmed by the parser to support code spans that begin or end with a backtick:
```markdown
` ` or ` `
```
Note: A single leading and trailing space is allowed by the specification and
automatically trimmed by the parser (in order to allow for code spans that embed
backticks):
```markdown ```markdown
`` `backticks` `` `` `backticks` ``
`` backtick` ``
``` ```
Note: A single leading or trailing space is allowed if used to separate code Note: When single-space padding is present in the input, it will be preserved
span markers from an embedded backtick (though the space is not trimmed): (even if unnecessary):
```markdown ```markdown
`` ` embedded backtick`` ` code `
``` ```
Rationale: Violations of this rule are usually unintentional and may lead to Note: Code spans containing only spaces are allowed by the specification and are
also preserved:
```markdown
` `
` `
```
Rationale: Violations of this rule are usually unintentional and can lead to
improperly-rendered content. improperly-rendered content.
<a name="md039"></a> <a name="md039"></a>

View file

@ -6,41 +6,47 @@ Aliases: `no-space-in-code`
Fixable: Some violations can be fixed by tooling Fixable: Some violations can be fixed by tooling
This rule is triggered for code spans that have content with spaces next to the This rule is triggered for code spans containing content with unnecessary space
beginning or ending backticks: next to the beginning or ending backticks:
```markdown ```markdown
`some text ` `some text `
` some text` ` some text`
` some text `
``` ```
To fix this, remove any spaces at the beginning or ending: To fix this, remove the extra space characters from the beginning and ending:
```markdown ```markdown
`some text` `some text`
``` ```
Note: Code spans containing only spaces are allowed by the specification: Note: A single leading *and* trailing space is allowed by the specification and
trimmed by the parser to support code spans that begin or end with a backtick:
```markdown
` ` or ` `
```
Note: A single leading and trailing space is allowed by the specification and
automatically trimmed by the parser (in order to allow for code spans that embed
backticks):
```markdown ```markdown
`` `backticks` `` `` `backticks` ``
`` backtick` ``
``` ```
Note: A single leading or trailing space is allowed if used to separate code Note: When single-space padding is present in the input, it will be preserved
span markers from an embedded backtick (though the space is not trimmed): (even if unnecessary):
```markdown ```markdown
`` ` embedded backtick`` ` code `
``` ```
Rationale: Violations of this rule are usually unintentional and may lead to Note: Code spans containing only spaces are allowed by the specification and are
also preserved:
```markdown
` `
` `
```
Rationale: Violations of this rule are usually unintentional and can lead to
improperly-rendered content. improperly-rendered content.

View file

@ -1,23 +1,9 @@
// @ts-check // @ts-check
import { addErrorContext } from "../helpers/helpers.cjs"; import { addErrorContext, newLineRe } from "../helpers/helpers.cjs";
import { getDescendantsByType } from "../helpers/micromark-helpers.cjs"; import { getDescendantsByType } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
const leftSpaceRe = /^\s(?:[^`]|$)/;
const rightSpaceRe = /[^`]\s$/;
const allSpaceRe = /^\s*$/;
const trimCodeText = (text, start, end) => {
text = text.replace(/^\s+$/, "");
if (start) {
text = text.replace(/^\s+?(\s`|\S)/, "$1");
}
if (end) {
text = text.replace(/(`\s|\S)\s+$/, "$1");
}
return text;
};
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
export default { export default {
"names": [ "MD038", "no-space-in-code" ], "names": [ "MD038", "no-space-in-code" ],
@ -27,68 +13,59 @@ export default {
"function": function MD038(params, onError) { "function": function MD038(params, onError) {
const codeTexts = filterByTypesCached([ "codeText" ]); const codeTexts = filterByTypesCached([ "codeText" ]);
for (const codeText of codeTexts) { for (const codeText of codeTexts) {
const sequences = getDescendantsByType(codeText, [ "codeTextSequence" ]);
const startSequence = sequences[0];
const endSequence = sequences[sequences.length - 1];
const datas = getDescendantsByType(codeText, [ "codeTextData" ]); const datas = getDescendantsByType(codeText, [ "codeTextData" ]);
const startData = datas[0]; if (datas.length > 0) {
const endData = datas[datas.length - 1]; const paddings = getDescendantsByType(codeText, [ "codeTextPadding" ]);
if (startSequence && endSequence && startData && endData) { // Check for extra space at start of code
const spaceLeft = leftSpaceRe.test(startData.text); const startPadding = paddings[0];
const spaceRight = rightSpaceRe.test(endData.text); const startData = datas[0];
if ( const startMatch = /^(\s+)(\S)/.exec(startData.text) || [ null, "", "" ];
(spaceLeft || spaceRight) && const startBacktick = (startMatch[2] === "`");
!datas.every((data) => allSpaceRe.test(data.text)) const startCount = startMatch[1].length - ((startBacktick && !startPadding) ? 1 : 0);
) { const startSpaces = startCount > 0;
let lineNumber = startSequence.startLine; // Check for extra space at end of code
let range = undefined; const endPadding = paddings[paddings.length - 1];
let fixInfo = undefined; const endData = datas[datas.length - 1];
if (startSequence.startLine === endSequence.endLine) { const endMatch = /(\S)(\s+)$/.exec(endData.text) || [ null, "", "" ];
range = [ const endBacktick = (endMatch[1] === "`");
startSequence.startColumn, const endCount = endMatch[2].length - ((endBacktick && !endPadding) ? 1 : 0);
endSequence.endColumn - startSequence.startColumn const endSpaces = endCount > 0;
]; // Check if safe to remove 1-space padding
fixInfo = { const removePadding = startSpaces && endSpaces && startPadding && endPadding && !startBacktick && !endBacktick;
"editColumn": startSequence.endColumn, const context = codeText.text.replace(newLineRe, "\n");
"deleteCount": endSequence.startColumn - startSequence.endColumn, // If extra space at start, report violation
"insertText": trimCodeText(startData.text, true, true) if (startSpaces) {
}; const startColumn = (removePadding ? startPadding : startData).startColumn;
} else if (spaceLeft && (startSequence.endLine === startData.startLine)) { const length = startCount + (removePadding ? startPadding.text.length : 0);
range = [ addErrorContext(
startSequence.startColumn, onError,
startData.endColumn - startSequence.startColumn startData.startLine,
]; context,
fixInfo = { true,
"editColumn": startSequence.endColumn, false,
"deleteCount": startData.endColumn - startData.startColumn, [ startColumn, length ],
"insertText": trimCodeText(startData.text, true, false) {
}; "editColumn": startColumn,
} else if (spaceRight && (endData.text.trim().length > 0)) { "deleteCount": length
lineNumber = endSequence.endLine; }
range = [ );
endData.startColumn, }
endSequence.endColumn - endData.startColumn // If extra space at end, report violation
]; if (endSpaces) {
fixInfo = { const endColumn = (removePadding ? endPadding : endData).endColumn;
"editColumn": endData.startColumn, const length = endCount + (removePadding ? endPadding.text.length : 0);
"deleteCount": endData.endColumn - endData.startColumn, addErrorContext(
"insertText": trimCodeText(endData.text, false, true) onError,
}; endData.endLine,
} context,
if (range) { false,
const context = params true,
.lines[lineNumber - 1] [ endColumn - length, length ],
.substring(range[0] - 1, range[0] - 1 + range[1]); {
addErrorContext( "editColumn": endColumn - length,
onError, "deleteCount": length
lineNumber, }
context, );
spaceLeft,
spaceRight,
range,
fixInfo
);
}
} }
} }
} }

View file

@ -54,6 +54,6 @@
`` `code `` (fixed) `` `code `` (fixed)
`` `code` `` {Could be MD038} `` `code` `` {MD038}
`` `code` `` (fixed) `` `code` `` (fixed)

File diff suppressed because it is too large Load diff

View file

@ -60,11 +60,11 @@ text and ``\`code with ignored escaped \` backticks``
` `` ` text `code` ` `` ` text `code`
``` ` leading space allowed for backtick``` text `code` ``` ` surrounding space allowed for backtick ``` text `code`
``` ` multiple leading spaces not allowed``` text `code` {MD038} ``` ` multiple leading spaces not allowed``` text `code` {MD038}
``trailing space allowed for backtick ` `` text `code` `` surrounding space allowed for backtick ` `` text `code`
``multiple trailing spaces not allowed ` `` text `code` {MD038} ``multiple trailing spaces not allowed ` `` text `code` {MD038}
@ -126,13 +126,13 @@ Again, 2 characters: ` ab `
Again, 1 character: ` a ` Again, 1 character: ` a `
Many internal spaces: ` code code code code code code ` Many internal spaces: ` code code code code code code `
text ``` ` leading space text ``` ` surrounding space
allowed for backtick``` text allowed for backtick ``` text
text ``` ` multiple leading {MD038} text ``` ` multiple leading {MD038}
spaces not allowed``` text spaces not allowed``` text
text ``trailing space text `` surrounding space
allowed for backtick ` `` text allowed for backtick ` `` text
text ``multiple trailing spaces text ``multiple trailing spaces
@ -150,7 +150,7 @@ Code
Text Text
``` ```
Code Code {MD038}
``` ```
Text Text
@ -160,7 +160,7 @@ Code
Text Text
``` ```
Code Code {MD038}
``` ```
Text Text