mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-16 14:00:13 +01:00
Reimplement MD039/no-space-in-links using micromark tokens.
This commit is contained in:
parent
92a19b6f36
commit
daa155d5a1
6 changed files with 265 additions and 124 deletions
|
|
@ -5656,10 +5656,9 @@ module.exports = {
|
|||
|
||||
|
||||
|
||||
const { addErrorContext, filterTokens } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js");
|
||||
|
||||
const spaceInLinkRe =
|
||||
/\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/;
|
||||
const { addErrorContext } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js");
|
||||
const { filterByTypes } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs");
|
||||
const { referenceLinkImageData } = __webpack_require__(/*! ./cache */ "../lib/cache.js");
|
||||
|
||||
// eslint-disable-next-line jsdoc/valid-types
|
||||
/** @type import("./markdownlint").Rule */
|
||||
|
|
@ -5667,60 +5666,55 @@ module.exports = {
|
|||
"names": [ "MD039", "no-space-in-links" ],
|
||||
"description": "Spaces inside link text",
|
||||
"tags": [ "whitespace", "links" ],
|
||||
"parser": "markdownit",
|
||||
"parser": "micromark",
|
||||
"function": function MD039(params, onError) {
|
||||
filterTokens(params, "inline", (token) => {
|
||||
const { children } = token;
|
||||
let { lineNumber } = token;
|
||||
let inLink = false;
|
||||
let linkText = "";
|
||||
let lineIndex = 0;
|
||||
for (const child of children) {
|
||||
const { content, markup, type } = child;
|
||||
if (type === "link_open") {
|
||||
inLink = true;
|
||||
linkText = "";
|
||||
} else if (type === "link_close") {
|
||||
inLink = false;
|
||||
const left = linkText.trimStart().length !== linkText.length;
|
||||
const right = linkText.trimEnd().length !== linkText.length;
|
||||
if (left || right) {
|
||||
const line = params.lines[lineNumber - 1];
|
||||
let range = null;
|
||||
let fixInfo = null;
|
||||
const match = line.slice(lineIndex).match(spaceInLinkRe);
|
||||
if (match) {
|
||||
// @ts-ignore
|
||||
const column = match.index + lineIndex + 1;
|
||||
const length = match[0].length;
|
||||
range = [ column, length ];
|
||||
fixInfo = {
|
||||
"editColumn": column + 1,
|
||||
"deleteCount": length - 2,
|
||||
"insertText": linkText.trim()
|
||||
};
|
||||
lineIndex = column + length - 1;
|
||||
}
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
`[${linkText}]`,
|
||||
left,
|
||||
right,
|
||||
range,
|
||||
fixInfo
|
||||
);
|
||||
const { definitions } = referenceLinkImageData();
|
||||
const labels = filterByTypes(
|
||||
params.parsers.micromark.tokens,
|
||||
[ "label" ]
|
||||
).filter((label) => label.parent?.type === "link");
|
||||
for (const label of labels) {
|
||||
const labelTexts = filterByTypes(label.children, [ "labelText" ]);
|
||||
for (const labelText of labelTexts) {
|
||||
const leftSpace =
|
||||
labelText.text.trimStart().length !== labelText.text.length;
|
||||
const rightSpace =
|
||||
labelText.text.trimEnd().length !== labelText.text.length;
|
||||
if (
|
||||
(leftSpace || rightSpace) &&
|
||||
// Ignore non-shortcut link content "[ text ]"
|
||||
((label.parent?.children.length !== 1) || definitions.has(labelText.text.trim()))
|
||||
) {
|
||||
// eslint-disable-next-line no-undef-init
|
||||
let range = undefined;
|
||||
if (label.startLine === label.endLine) {
|
||||
const labelColumn = label.startColumn;
|
||||
const labelLength = label.endColumn - label.startColumn;
|
||||
range = [ labelColumn, labelLength ];
|
||||
}
|
||||
} else if ((type === "softbreak") || (type === "hardbreak")) {
|
||||
lineNumber++;
|
||||
lineIndex = 0;
|
||||
} else if (inLink) {
|
||||
linkText += type.endsWith("_inline") ?
|
||||
`${markup}${content}${markup}` :
|
||||
(content || markup);
|
||||
// eslint-disable-next-line no-undef-init
|
||||
let fixInfo = undefined;
|
||||
if (labelText.startLine === labelText.endLine) {
|
||||
const textColumn = labelText.startColumn;
|
||||
const textLength = labelText.endColumn - labelText.startColumn;
|
||||
fixInfo = {
|
||||
"editColumn": textColumn,
|
||||
"deleteCount": textLength,
|
||||
"insertText": labelText.text.trim()
|
||||
};
|
||||
}
|
||||
addErrorContext(
|
||||
onError,
|
||||
labelText.startLine,
|
||||
label.text.replace(/\s+/g, " "),
|
||||
leftSpace,
|
||||
rightSpace,
|
||||
range,
|
||||
fixInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
102
lib/md039.js
102
lib/md039.js
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { addErrorContext, filterTokens } = require("../helpers");
|
||||
|
||||
const spaceInLinkRe =
|
||||
/\[(?:\s[^\]]*|[^\]]*?\s)\](?=(\([^)]*\)|\[[^\]]*\]))/;
|
||||
const { addErrorContext } = require("../helpers");
|
||||
const { filterByTypes } = require("../helpers/micromark.cjs");
|
||||
const { referenceLinkImageData } = require("./cache");
|
||||
|
||||
// eslint-disable-next-line jsdoc/valid-types
|
||||
/** @type import("./markdownlint").Rule */
|
||||
|
|
@ -13,59 +12,54 @@ module.exports = {
|
|||
"names": [ "MD039", "no-space-in-links" ],
|
||||
"description": "Spaces inside link text",
|
||||
"tags": [ "whitespace", "links" ],
|
||||
"parser": "markdownit",
|
||||
"parser": "micromark",
|
||||
"function": function MD039(params, onError) {
|
||||
filterTokens(params, "inline", (token) => {
|
||||
const { children } = token;
|
||||
let { lineNumber } = token;
|
||||
let inLink = false;
|
||||
let linkText = "";
|
||||
let lineIndex = 0;
|
||||
for (const child of children) {
|
||||
const { content, markup, type } = child;
|
||||
if (type === "link_open") {
|
||||
inLink = true;
|
||||
linkText = "";
|
||||
} else if (type === "link_close") {
|
||||
inLink = false;
|
||||
const left = linkText.trimStart().length !== linkText.length;
|
||||
const right = linkText.trimEnd().length !== linkText.length;
|
||||
if (left || right) {
|
||||
const line = params.lines[lineNumber - 1];
|
||||
let range = null;
|
||||
let fixInfo = null;
|
||||
const match = line.slice(lineIndex).match(spaceInLinkRe);
|
||||
if (match) {
|
||||
// @ts-ignore
|
||||
const column = match.index + lineIndex + 1;
|
||||
const length = match[0].length;
|
||||
range = [ column, length ];
|
||||
fixInfo = {
|
||||
"editColumn": column + 1,
|
||||
"deleteCount": length - 2,
|
||||
"insertText": linkText.trim()
|
||||
};
|
||||
lineIndex = column + length - 1;
|
||||
}
|
||||
addErrorContext(
|
||||
onError,
|
||||
lineNumber,
|
||||
`[${linkText}]`,
|
||||
left,
|
||||
right,
|
||||
range,
|
||||
fixInfo
|
||||
);
|
||||
const { definitions } = referenceLinkImageData();
|
||||
const labels = filterByTypes(
|
||||
params.parsers.micromark.tokens,
|
||||
[ "label" ]
|
||||
).filter((label) => label.parent?.type === "link");
|
||||
for (const label of labels) {
|
||||
const labelTexts = filterByTypes(label.children, [ "labelText" ]);
|
||||
for (const labelText of labelTexts) {
|
||||
const leftSpace =
|
||||
labelText.text.trimStart().length !== labelText.text.length;
|
||||
const rightSpace =
|
||||
labelText.text.trimEnd().length !== labelText.text.length;
|
||||
if (
|
||||
(leftSpace || rightSpace) &&
|
||||
// Ignore non-shortcut link content "[ text ]"
|
||||
((label.parent?.children.length !== 1) || definitions.has(labelText.text.trim()))
|
||||
) {
|
||||
// eslint-disable-next-line no-undef-init
|
||||
let range = undefined;
|
||||
if (label.startLine === label.endLine) {
|
||||
const labelColumn = label.startColumn;
|
||||
const labelLength = label.endColumn - label.startColumn;
|
||||
range = [ labelColumn, labelLength ];
|
||||
}
|
||||
} else if ((type === "softbreak") || (type === "hardbreak")) {
|
||||
lineNumber++;
|
||||
lineIndex = 0;
|
||||
} else if (inLink) {
|
||||
linkText += type.endsWith("_inline") ?
|
||||
`${markup}${content}${markup}` :
|
||||
(content || markup);
|
||||
// eslint-disable-next-line no-undef-init
|
||||
let fixInfo = undefined;
|
||||
if (labelText.startLine === labelText.endLine) {
|
||||
const textColumn = labelText.startColumn;
|
||||
const textLength = labelText.endColumn - labelText.startColumn;
|
||||
fixInfo = {
|
||||
"editColumn": textColumn,
|
||||
"deleteCount": textLength,
|
||||
"insertText": labelText.text.trim()
|
||||
};
|
||||
}
|
||||
addErrorContext(
|
||||
onError,
|
||||
labelText.startLine,
|
||||
label.text.replace(/\s+/g, " "),
|
||||
leftSpace,
|
||||
rightSpace,
|
||||
range,
|
||||
fixInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1104,20 +1104,13 @@ test("someCustomRulesHaveValidUrl", (t) => {
|
|||
});
|
||||
|
||||
test("markdownItPluginsSingle", (t) => new Promise((resolve) => {
|
||||
t.plan(2);
|
||||
t.plan(4);
|
||||
markdownlint({
|
||||
"strings": {
|
||||
"string": "# Heading\n\nText [ link ](https://example.com)\n"
|
||||
"string": "# Heading\n\nText\n"
|
||||
},
|
||||
"markdownItPlugins": [
|
||||
[
|
||||
pluginInline,
|
||||
"trim_text_plugin",
|
||||
"text",
|
||||
function iterator(tokens, index) {
|
||||
tokens[index].content = tokens[index].content.trim();
|
||||
}
|
||||
]
|
||||
[ pluginInline, "check_text_plugin", "text", () => t.true(true) ]
|
||||
]
|
||||
}, function callback(err, actual) {
|
||||
t.falsy(err);
|
||||
|
|
|
|||
|
|
@ -51692,11 +51692,11 @@ Generated by [AVA](https://avajs.dev).
|
|||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ link with leading space]',
|
||||
errorContext: '[ link with lea...space {MD039} ]',
|
||||
errorDetail: null,
|
||||
errorRange: null,
|
||||
fixInfo: null,
|
||||
lineNumber: 52,
|
||||
lineNumber: 51,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
|
|
@ -51784,6 +51784,126 @@ Generated by [AVA](https://avajs.dev).
|
|||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ref ]',
|
||||
errorDetail: null,
|
||||
errorRange: [
|
||||
1,
|
||||
6,
|
||||
],
|
||||
fixInfo: {
|
||||
deleteCount: 4,
|
||||
editColumn: 2,
|
||||
insertText: 'ref',
|
||||
},
|
||||
lineNumber: 68,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
'MD039',
|
||||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ ref]',
|
||||
errorDetail: null,
|
||||
errorRange: [
|
||||
1,
|
||||
6,
|
||||
],
|
||||
fixInfo: {
|
||||
deleteCount: 4,
|
||||
editColumn: 2,
|
||||
insertText: 'ref',
|
||||
},
|
||||
lineNumber: 70,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
'MD039',
|
||||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ ref ]',
|
||||
errorDetail: null,
|
||||
errorRange: [
|
||||
1,
|
||||
7,
|
||||
],
|
||||
fixInfo: {
|
||||
deleteCount: 5,
|
||||
editColumn: 2,
|
||||
insertText: 'ref',
|
||||
},
|
||||
lineNumber: 72,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
'MD039',
|
||||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ref ]',
|
||||
errorDetail: null,
|
||||
errorRange: [
|
||||
1,
|
||||
6,
|
||||
],
|
||||
fixInfo: {
|
||||
deleteCount: 4,
|
||||
editColumn: 2,
|
||||
insertText: 'ref',
|
||||
},
|
||||
lineNumber: 76,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
'MD039',
|
||||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ ref]',
|
||||
errorDetail: null,
|
||||
errorRange: [
|
||||
1,
|
||||
6,
|
||||
],
|
||||
fixInfo: {
|
||||
deleteCount: 4,
|
||||
editColumn: 2,
|
||||
insertText: 'ref',
|
||||
},
|
||||
lineNumber: 78,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
'MD039',
|
||||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
{
|
||||
errorContext: '[ ref ]',
|
||||
errorDetail: null,
|
||||
errorRange: [
|
||||
1,
|
||||
7,
|
||||
],
|
||||
fixInfo: {
|
||||
deleteCount: 5,
|
||||
editColumn: 2,
|
||||
insertText: 'ref',
|
||||
},
|
||||
lineNumber: 80,
|
||||
ruleDescription: 'Spaces inside link text',
|
||||
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md039.md',
|
||||
ruleNames: [
|
||||
'MD039',
|
||||
'no-space-in-links',
|
||||
],
|
||||
},
|
||||
],
|
||||
fixed: `# Spaces Inside Link Text␊
|
||||
␊
|
||||
|
|
@ -51835,8 +51955,8 @@ Generated by [AVA](https://avajs.dev).
|
|||
[with](spaces) ␊
|
||||
[error]({MD039})␊
|
||||
␊
|
||||
Wrapped [ link with leading space␊
|
||||
](https://example.com) {MD039}␊
|
||||
Wrapped [ link with leading space {MD039}␊
|
||||
](https://example.com)␊
|
||||
␊
|
||||
Non-wrapped [link with leading space](https://example.com) {MD039}␊
|
||||
␊
|
||||
|
|
@ -51850,7 +51970,27 @@ Generated by [AVA](https://avajs.dev).
|
|||
␊
|
||||
[link][ref] {MD039}␊
|
||||
␊
|
||||
[ref]␊
|
||||
␊
|
||||
[ref] {MD039}␊
|
||||
␊
|
||||
[ref] {MD039}␊
|
||||
␊
|
||||
[ref] {MD039}␊
|
||||
␊
|
||||
[ref][]␊
|
||||
␊
|
||||
[ref][] {MD039}␊
|
||||
␊
|
||||
[ref][] {MD039}␊
|
||||
␊
|
||||
[ref][] {MD039}␊
|
||||
␊
|
||||
[ref]: https://example.com␊
|
||||
␊
|
||||
Not a link, just [ text in ] brackets␊
|
||||
␊
|
||||
Images are ␊
|
||||
`,
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -48,8 +48,8 @@ function MoreCodeButNotCode(input) {
|
|||
[with](spaces)
|
||||
[error ]({MD039})
|
||||
|
||||
Wrapped [ link with leading space
|
||||
](https://example.com) {MD039}
|
||||
Wrapped [ link with leading space {MD039}
|
||||
](https://example.com)
|
||||
|
||||
Non-wrapped [ link with leading space](https://example.com) {MD039}
|
||||
|
||||
|
|
@ -63,4 +63,24 @@ Non-wrapped [ link with leading space](https://example.com) {MD039}
|
|||
|
||||
[ link ][ref] {MD039}
|
||||
|
||||
[ref]
|
||||
|
||||
[ref ] {MD039}
|
||||
|
||||
[ ref] {MD039}
|
||||
|
||||
[ ref ] {MD039}
|
||||
|
||||
[ref][]
|
||||
|
||||
[ref ][] {MD039}
|
||||
|
||||
[ ref][] {MD039}
|
||||
|
||||
[ ref ][] {MD039}
|
||||
|
||||
[ref]: https://example.com
|
||||
|
||||
Not a link, just [ text in ] brackets
|
||||
|
||||
Images are 
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue