Reimplement MD037/no-space-in-emphasis using micromark tokens, report start/end separately for smaller edit spans, remove markdown-it-texmath (fixes #533, fixes #597).

This commit is contained in:
David Anson 2023-07-24 21:36:55 -07:00
parent e86fb7699d
commit 73b9704159
20 changed files with 1784 additions and 2502 deletions

View file

@ -52,14 +52,8 @@ t<!--> *{MD037} * -->
t<!---> *{MD037} * -->
t<!-- *{MD037} * --->
t<!-- *comment * --->
t<!-- -- *{MD037} * -->
t<!-- -- *comment * -->
t<!-- *{MD037} * -- -->
## Notes
It's important that the rule used above is one that calls
`helpers.forEachLine` so `markdown-it` doesn't ignore any
incorrectly-remaining comment blocks.
t<!-- *comment * -- -->

View file

@ -895,333 +895,6 @@ test("applyFixes", (t) => {
}
});
test("forEachLink", (t) => {
t.plan(291);
const testCases = [
[
"",
[]
],
[
"Text",
[]
],
[
"Text [text] (text) text",
[ [ 5, "[text]", "[text]", undefined ] ]
],
[
"Text [text] text (text) text",
[ [ 5, "[text]", "[text]", undefined ] ]
],
[
"Text [text] [text] text",
[
[ 5, "[text]", "[text]", undefined ],
[ 12, "[text]", "[text]", undefined ]
]
],
[
"Text [text] text [text] text",
[
[ 5, "[text]", "[text]", undefined ],
[ 17, "[text]", "[text]", undefined ]
]
],
[
"Text [link](destination) text",
[ [ 5, "[link](destination)", "[link]", "(destination)" ] ]
],
[
"Text [link0](destination0) text [link1](destination1) text",
[
[ 5, "[link0](destination0)", "[link0]", "(destination0)" ],
[ 32, "[link1](destination1)", "[link1]", "(destination1)" ]
]
],
[
"Text [link0] text [link1](destination1) text [link2] text",
[
[ 5, "[link0]", "[link0]", undefined ],
[ 18, "[link1](destination1)", "[link1]", "(destination1)" ],
[ 45, "[link2]", "[link2]", undefined ]
]
],
[
"Text [link0](destination0) text [link1] text [link2](destination2) text",
[
[ 5, "[link0](destination0)", "[link0]", "(destination0)" ],
[ 32, "[link1]", "[link1]", undefined ],
[ 45, "[link2](destination2)", "[link2]", "(destination2)" ]
]
],
[
"Text [link0][destination0] text [link1] text [link2](destination2) text",
[
[ 5, "[link0][destination0]", "[link0]", "[destination0]" ],
[ 32, "[link1]", "[link1]", undefined ],
[ 45, "[link2](destination2)", "[link2]", "(destination2)" ]
]
],
[
"Text [link0](destination0) text [link1] text [link2][destination2] text",
[
[ 5, "[link0](destination0)", "[link0]", "(destination0)" ],
[ 32, "[link1]", "[link1]", undefined ],
[ 45, "[link2][destination2]", "[link2]", "[destination2]" ]
]
],
[
"Text [link](destination \"title\") text",
[
[
5,
"[link](destination \"title\")",
"[link]",
"(destination \"title\")"
]
]
],
[
"Text [link](<destination> \"title\") text",
[
[
5,
"[link](<destination> \"title\")",
"[link]",
"(<destination> \"title\")"
]
]
],
[
"Text [link](destination \"ti\\\"tle\") text",
[
[
5,
"[link](destination \"ti\\\"tle\")",
"[link]",
"(destination \"ti\\\"tle\")"
]
]
],
[
"Text [link](destination 'title') text",
[
[
5,
"[link](destination 'title')",
"[link]",
"(destination 'title')"
]
]
],
[
"Text [link](destination (title)) text",
[
[
5,
"[link](destination (title))",
"[link]",
"(destination (title))"
]
]
],
[
"Text [link](\"title\") text",
[ [ 5, "[link](\"title\")", "[link]", "(\"title\")" ] ]
],
[
"[]()",
[ [ 0, "[]()", "[]", "()" ] ]
],
[
"[l](d)",
[ [ 0, "[l](d)", "[l]", "(d)" ] ]
],
[
"Text [li[nk](dest) text",
[ [ 8, "[nk](dest)", "[nk]", "(dest)" ] ]
],
[
"Text [li\\[nk](dest) text",
[ [ 5, "[li\\[nk](dest)", "[li\\[nk]", "(dest)" ] ]
],
[
"Text [li]nk](dest) text",
[ [ 5, "[li]", "[li]", undefined ] ]
],
[
"Text [li\\]nk](dest) text",
[ [ 5, "[li\\]nk](dest)", "[li\\]nk]", "(dest)" ] ]
],
[
"Text [l[in]k](dest) text",
[ [ 5, "[l[in]k](dest)", "[l[in]k]", "(dest)" ] ]
],
[
"Text [li(nk](dest) text",
[ [ 5, "[li(nk](dest)", "[li(nk]", "(dest)" ] ]
],
[
"Text [li)nk](dest) text",
[ [ 5, "[li)nk](dest)", "[li)nk]", "(dest)" ] ]
],
[
"Text [l(in)k](dest) text",
[ [ 5, "[l(in)k](dest)", "[l(in)k]", "(dest)" ] ]
],
[
"Text [link](de(st) text",
[ [ 5, "[link]", "[link]", undefined ] ]
],
[
"Text [link](de\\(st) text",
[ [ 5, "[link](de\\(st)", "[link]", "(de\\(st)" ] ]
],
[
"Text [link](de)st) text",
[ [ 5, "[link](de)", "[link]", "(de)" ] ]
],
[
"Text [link](de\\)st) text",
[ [ 5, "[link](de\\)st)", "[link]", "(de\\)st)" ] ]
],
[
"Text [link](d(es)t) text",
[ [ 5, "[link](d(es)t)", "[link]", "(d(es)t)" ] ]
],
[
"Text [link]() text",
[ [ 5, "[link]()", "[link]", "()" ] ]
],
[
"Text [link](#) text",
[ [ 5, "[link](#)", "[link]", "(#)" ] ]
],
[
"Text [link](<de) text",
[ [ 5, "[link]", "[link]", undefined ] ]
],
[
"Text [link](<de)st> text",
[ [ 5, "[link]", "[link]", undefined ] ]
],
[
"Text [link](<>) text",
[ [ 5, "[link](<>)", "[link]", "(<>)" ] ]
],
[
"Text [link](<dest>) text",
[ [ 5, "[link](<dest>)", "[link]", "(<dest>)" ] ]
],
[
"Text [link](<de st>) text",
[ [ 5, "[link](<de st>)", "[link]", "(<de st>)" ] ]
],
[
"Text [link](<de)st>) text",
[ [ 5, "[link](<de)st>)", "[link]", "(<de)st>)" ] ]
],
[
"Text [<link](dest) text",
[ [ 5, "[<link](dest)", "[<link]", "(dest)" ] ]
],
[
"Text [<link>](dest) text",
[ [ 5, "[<link>](dest)", "[<link>]", "(dest)" ] ]
],
[
"Text [<link>](<dest) text",
[ [ 5, "[<link>]", "[<link>]", undefined ] ]
],
[
"Text [<link>](<dest>) text",
[ [ 5, "[<link>](<dest>)", "[<link>]", "(<dest>)" ] ]
],
[
"Text [[[[l[i]n[k]](dest) text",
[ [ 8, "[l[i]n[k]](dest)", "[l[i]n[k]]", "(dest)" ] ]
],
[
"Text [link](d(e(st))) text",
[ [ 5, "[link](d(e(st)))", "[link]", "(d(e(st)))" ] ]
],
[
"Text [link](d(e(st)) text",
[ [ 5, "[link]", "[link]", undefined ] ]
],
[
"Text [link](<d(e(st)>) text",
[ [ 5, "[link](<d(e(st)>)", "[link]", "(<d(e(st)>)" ] ]
],
[
"Text [link][reference] text",
[ [ 5, "[link][reference]", "[link]", "[reference]" ] ]
],
[
"Text [link][refer]ence] text",
[ [ 5, "[link][refer]", "[link]", "[refer]" ] ]
]
];
for (const testCase of testCases) {
const [ markdown, matches ] = testCase;
helpers.forEachLink(String(markdown), (idx, lnk, txt, des) => {
// @ts-ignore
const match = matches.shift();
const [ index, link, text, destination ] = match;
t.is(idx, index, String(markdown));
t.is(lnk, link, String(markdown));
t.is(txt, text, String(markdown));
t.is(des, destination, String(markdown));
});
t.is(matches.length, 0, "Missing match");
}
});
test("htmlElementRanges", (t) => {
t.plan(1);
const params = {
"lines": [
"# Heading",
"",
"Text text text",
"text <a id='id'/> text",
"text text text",
"",
"<p>",
"Text <em>text</em> text",
"</p>",
"",
"```",
"<br/>",
"```",
"",
"Text `<br/>` text",
"text <br/> text"
],
"parsers": {
"markdownit": {
"tokens": [
{
"type": "code_block",
"map": [ 10, 12 ]
}
]
}
}
};
const expected = [
[ 3, 5, 12 ],
[ 6, 0, 3 ],
[ 7, 5, 4 ],
[ 14, 6, 5 ],
[ 15, 5, 5 ]
];
const lineMetadata = helpers.getLineMetadata(params);
const actual = helpers.htmlElementRanges(params, lineMetadata);
t.deepEqual(actual, expected);
});
test("expandTildePath", (t) => {
t.plan(17);
const homedir = os.homedir();

View file

@ -406,12 +406,24 @@ test("resultFormattingV3", (t) => new Promise((resolve) => {
"ruleDescription": "Spaces inside emphasis markers",
"ruleInformation": `${homepage}/blob/v${version}/doc/md037.md`,
"errorDetail": null,
"errorContext": "* emphasis *",
"errorRange": [ 6, 12 ],
"errorContext": "* e",
"errorRange": [ 6, 3 ],
"fixInfo": {
"editColumn": 6,
"deleteCount": 12,
"insertText": "*emphasis*"
"editColumn": 7,
"deleteCount": 1
}
},
{
"lineNumber": 4,
"ruleNames": [ "MD037", "no-space-in-emphasis" ],
"ruleDescription": "Spaces inside emphasis markers",
"ruleInformation": `${homepage}/blob/v${version}/doc/md037.md`,
"errorDetail": null,
"errorContext": "s *",
"errorRange": [ 15, 3 ],
"fixInfo": {
"editColumn": 16,
"deleteCount": 1
}
},
{
@ -439,7 +451,9 @@ test("resultFormattingV3", (t) => new Promise((resolve) => {
"input: 3: MD010/no-hard-tabs" +
" Hard tabs [Column: 10]\n" +
"input: 4: MD037/no-space-in-emphasis" +
" Spaces inside emphasis markers [Context: \"* emphasis *\"]\n" +
" Spaces inside emphasis markers [Context: \"* e\"]\n" +
"input: 4: MD037/no-space-in-emphasis" +
" Spaces inside emphasis markers [Context: \"s *\"]\n" +
"input: 4: MD047/single-trailing-newline" +
" Files should end with a single newline character";
t.is(actualMessage, expectedMessage, "Incorrect message.");

View file

@ -9,7 +9,6 @@ const md = require("markdown-it")();
const pluginInline = require("markdown-it-for-inline");
const pluginSub = require("markdown-it-sub");
const pluginSup = require("markdown-it-sup");
const pluginTexMath = require("markdown-it-texmath");
const test = require("ava").default;
const tv4 = require("tv4");
const { "exports": packageExports, homepage, version } =
@ -20,11 +19,6 @@ const rules = require("../lib/rules");
const customRules = require("./rules/rules.js");
const configSchema = require("../schema/markdownlint-config-schema.json");
const pluginTexMathOptions = {
"engine": {
"renderToString": () => ""
}
};
const deprecatedRuleNames = new Set(constants.deprecatedRuleNames);
const configSchemaStrict = {
...configSchema,
@ -1104,77 +1098,6 @@ test("markdownItPluginsMultiple", (t) => new Promise((resolve) => {
});
}));
test("markdownItPluginsMathjax", (t) => new Promise((resolve) => {
t.plan(2);
markdownlint({
"strings": {
"string":
"# Heading\n" +
"\n" +
"$1 *2* 3$\n" +
"\n" +
"$$1 *2* 3$$\n" +
"\n" +
"$$1\n" +
"+ 2\n" +
"+ 3$$\n"
},
"markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ]
}, function callback(err, actual) {
t.falsy(err);
const expected = { "string": [] };
t.deepEqual(actual, expected, "Unexpected issues.");
resolve();
});
}));
test("markdownItPluginsMathjaxIssue166", (t) => new Promise((resolve) => {
t.plan(2);
markdownlint({
"strings": {
"string":
`## Heading
$$
1
$$$$
2
$$\n`
},
"markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ],
"resultVersion": 0
}, function callback(err, actual) {
t.falsy(err);
const expected = {
"string": {
"MD041": [ 1 ]
}
};
// @ts-ignore
t.deepEqual(actual, expected, "Unexpected issues.");
resolve();
});
}));
test("texmath test files with texmath plugin", (t) => new Promise((resolve) => {
t.plan(2);
markdownlint({
"files": [
"./test/texmath-content-in-lists.md",
"./test/texmath-content-violating-md037.md"
],
"markdownItPlugins": [ [ pluginTexMath, pluginTexMathOptions ] ]
}, function callback(err, actual) {
t.falsy(err);
const expected = {
"./test/texmath-content-in-lists.md": [],
"./test/texmath-content-violating-md037.md": []
};
t.deepEqual(actual, expected, "Unexpected issues.");
resolve();
});
}));
test("Pandoc footnote", (t) => new Promise((resolve) => {
t.plan(2);
markdownlint({

15
test/mathjax-scenarios.md Normal file
View file

@ -0,0 +1,15 @@
# Mathjax Scenarios
$1 * 2 * 3$
$$1 * 2 * 3$$
$$1
+ 2
+ 3$$
$$
1
$$$$
2
$$

View file

@ -17,7 +17,9 @@ Generated by [AVA](https://avajs.dev).
`test-repos/dotnet-docs/docs/azure/sdk/includes/assign-local-dev-group-to-role-azure-portal-4.md: 5: MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "<br>"]␊
test-repos/dotnet-docs/docs/azure/sdk/includes/assign-managed-identity-to-role-azure-portal-4.md: 5: MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "<br>"]␊
test-repos/dotnet-docs/docs/azure/sdk/includes/assign-service-principal-to-role-azure-portal-4.md: 5: MD032/blanks-around-lists Lists should be surrounded by blank lines [Context: "<br>"]␊
test-repos/dotnet-docs/docs/core/compatibility/core-libraries/5.0/code-access-security-apis-obsolete.md: 431: MD033/no-inline-html Inline HTML [Element: StrongName]`
test-repos/dotnet-docs/docs/core/compatibility/core-libraries/5.0/code-access-security-apis-obsolete.md: 431: MD033/no-inline-html Inline HTML [Element: StrongName]␊
test-repos/dotnet-docs/docs/core/extensions/windows-service.md: 177: MD037/no-space-in-emphasis Spaces inside emphasis markers [Context: "n *"]␊
test-repos/dotnet-docs/docs/devops/github-actions-overview.md: 32: MD037/no-space-in-emphasis Spaces inside emphasis markers [Context: "r *"]`
## https://github.com/electron/electron

File diff suppressed because it is too large Load diff

View file

@ -42,8 +42,8 @@ emphasis * text {MD037}
Text * emphasis {MD037}
emphasis * text {MD037}
Text *emphasis * *emphasis {MD037}
emphasis* * emphasis* text {MD037}
Text *emphasis * *emphasis
emphasis* * emphasis* text
Text *emphasis* * emphasis {MD037}
emphasis * *emphasis* text {MD037}

View file

@ -125,32 +125,51 @@ Uncommon scenarios from the CommonMark specification (and some variations):
*in emph **strong***
*** strong emph*** {MD037}
*** strong** in emph* {MD037}
*** emph* in strong** {MD037}
*** strong** in emph* {possible MD037}
*** emph* in strong** {possible MD037}
** in strong *emph*** {MD037}
***strong emph *** {MD037}
***strong** in emph * {MD037}
***emph* in strong ** {MD037}
**in strong *emph *** {MD037}
*in emph **strong *** {MD037}
**in strong *emph *** {possible MD037}
*in emph **strong *** {possible MD037}
** *strong emph*** {MD037}
** *strong** in emph* {MD037}
** *emph* in strong** {MD037}
**in strong * emph*** (internal spaces are not detected)
*in emph ** strong*** (internal spaces are not detected)
***strong emph* ** {MD037}
***strong ** in emph* (internal spaces are not detected)
***emph * in strong** (internal spaces are not detected)
**in strong *emph* ** {MD037}
*in emph **strong* ** {MD037}
Text *emph***strong** text
Text * emph***strong** text {MD037}
Text *emph ***strong** text (internal spaces are not detected)
Text *emph*** strong** text (internal spaces are not detected)
Text *emph ***strong** text {MD037}
Text *emph*** strong** text {MD037}
Text *emph***strong ** text {MD037}
```markdown

View file

@ -1,19 +0,0 @@
# texmath-content-violating-md037
## Inline (not handled)
text `$ x * y * z $` text
text `$$ x * y * z $$` text
## Block (handled when used with markdown-it-texmath)
$$
x * y * z {MD037}
$$
text
$$
x * y = x * y {MD037}
$$

19
test/texmath-content.md Normal file
View file

@ -0,0 +1,19 @@
# texmath-content
## Inline
text $ x * y * z $ text
text $$ x * y * z $$ text
## Block
$$
x * y * z
$$
text
$$
x * y = x * y
$$