Update previous commit for MD051/link-fragments to rename, refactor, add support for HTML anchors, and validate against

markdown-link-check (fixes #253).
This commit is contained in:
David Anson 2022-04-10 05:37:57 +00:00
parent 33ee1cd85e
commit db5d9f6dbb
21 changed files with 355 additions and 181 deletions

View file

@ -107,7 +107,7 @@ playground for learning and exploring.
* **[MD048](doc/Rules.md#md048)** *code-fence-style* - Code fence style * **[MD048](doc/Rules.md#md048)** *code-fence-style* - Code fence style
* **[MD049](doc/Rules.md#md049)** *emphasis-style* - Emphasis style should be consistent * **[MD049](doc/Rules.md#md049)** *emphasis-style* - Emphasis style should be consistent
* **[MD050](doc/Rules.md#md050)** *strong-style* - Strong style should be consistent * **[MD050](doc/Rules.md#md050)** *strong-style* - Strong style should be consistent
* **[MD051](doc/Rules.md#md051)** *valid-link-fragments* - Link fragments should be valid * **[MD051](doc/Rules.md#md051)** *link-fragments* - Link fragments should be valid
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

View file

@ -43,6 +43,8 @@ var inlineCommentStartRe =
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-next-line|configure-file))(?:\s|-->)/ig; /(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-next-line|configure-file))(?:\s|-->)/ig;
module.exports.inlineCommentStartRe = inlineCommentStartRe; module.exports.inlineCommentStartRe = inlineCommentStartRe;
// Regular expression for matching HTML elements
module.exports.htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g;
// Regular expressions for range matching // Regular expressions for range matching
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig; module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig;
module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/; module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
@ -424,7 +426,7 @@ module.exports.forEachHeading = function forEachHeading(params, handler) {
heading = null; heading = null;
} }
else if ((token.type === "inline") && heading) { else if ((token.type === "inline") && heading) {
handler(heading, token.content); handler(heading, token.content, token);
} }
}); });
}; };
@ -3414,9 +3416,8 @@ module.exports = {
"use strict"; "use strict";
// @ts-check // @ts-check
var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, overlapsAnyRange = _a.overlapsAnyRange, unescapeMarkdown = _a.unescapeMarkdown; var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachLine = _a.forEachLine, htmlElementRe = _a.htmlElementRe, overlapsAnyRange = _a.overlapsAnyRange, unescapeMarkdown = _a.unescapeMarkdown;
var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata; var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata;
var htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g;
var linkDestinationRe = /]\(\s*$/; var linkDestinationRe = /]\(\s*$/;
// See https://spec.commonmark.org/0.29/#autolinks // See https://spec.commonmark.org/0.29/#autolinks
var emailAddressRe = var emailAddressRe =
@ -4394,42 +4395,67 @@ module.exports = {
"use strict"; "use strict";
// @ts-check // @ts-check
var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, forEachHeading = _a.forEachHeading, filterTokens = _a.filterTokens; var _a = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), addError = _a.addError, escapeForRegExp = _a.escapeForRegExp, filterTokens = _a.filterTokens, forEachLine = _a.forEachLine, forEachHeading = _a.forEachHeading, htmlElementRe = _a.htmlElementRe, overlapsAnyRange = _a.overlapsAnyRange;
var _b = __webpack_require__(/*! ./cache */ "../lib/cache.js"), codeBlockAndSpanRanges = _b.codeBlockAndSpanRanges, lineMetadata = _b.lineMetadata;
// Regular expression for identifying HTML anchor names
var identifierRe = /(?:id|name)\s*=\s*['"]?([^'"\s>]+)/iu;
/** /**
* Converts a Markdown heading into an HTML fragment * Converts a Markdown heading into an HTML fragment according to the rules
* according to the rules used by GitHub. * used by GitHub.
* *
* @param {string} string The string to convert. * @param {Object} inline Inline token for heading.
* @returns {string} The converted string. * @returns {string} Fragment string for heading.
*/ */
function convertHeadingToHTMLFragment(string) { function convertHeadingToHTMLFragment(inline) {
return "#" + string var inlineText = inline.children.map(function (token) { return token.content; }).join("");
return "#" + inlineText
.toLowerCase() .toLowerCase()
.replace(/ /g, "-") .replace(/ /g, "-")
.replace(/[^-_a-z0-9]/g, ""); .replace(/[^-_a-z0-9]/g, "");
} }
module.exports = { module.exports = {
"names": ["MD051", "valid-link-fragments"], "names": ["MD051", "link-fragments"],
"description": "Link fragments should be valid", "description": "Link fragments should be valid",
"tags": ["links"], "tags": ["links"],
"function": function MD051(params, onError) { "function": function MD051(params, onError) {
var validLinkFragments = []; var fragments = new Set();
forEachHeading(params, function (_heading, content) { forEachHeading(params, function (heading, content, inline) {
validLinkFragments.push(convertHeadingToHTMLFragment(content)); fragments.add(convertHeadingToHTMLFragment(inline));
}); });
filterTokens(params, "inline", function (token) { var exclusions = codeBlockAndSpanRanges();
token.children.forEach(function (child) { forEachLine(lineMetadata(), function (line, lineIndex, inCode) {
var lineNumber = child.lineNumber, type = child.type, attrs = child.attrs; var match = null;
if (type === "link_open") { // eslint-disable-next-line no-unmodified-loop-condition
var href = attrs.find(function (attr) { return attr[0] === "href"; }); while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) {
if (href !== undefined && var tag = match[0], element = match[2];
href[1].startsWith("#") && if ((element.toLowerCase() === "a") &&
!validLinkFragments.includes(href[1])) { !overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length)) {
var detail = "Link Fragment is invalid"; var idMatch = identifierRe.exec(tag);
addError(onError, lineNumber, detail, href[1]); if (idMatch) {
fragments.add("#".concat(idMatch[1]));
} }
} }
}); }
});
filterTokens(params, "inline", function (token) {
for (var _i = 0, _a = token.children; _i < _a.length; _i++) {
var child = _a[_i];
var attrs = child.attrs, lineNumber = child.lineNumber, line = child.line, type = child.type;
if (type === "link_open") {
var href = attrs.find(function (attr) { return attr[0] === "href"; });
var id = href && href[1];
if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) {
var context = id;
var range = null;
var match = line.match(new RegExp("\\[.*?\\]\\(".concat(escapeForRegExp(context), "\\)")));
if (match) {
context = match[0];
range = [match.index + 1, match[0].length];
}
addError(onError, lineNumber, null, context, range);
}
}
}
}); });
} }
}; };

View file

@ -56,8 +56,7 @@ Aliases: first-heading-h1, first-header-h1
Parameters: level (number; default 1) Parameters: level (number; default 1)
> Note: *MD002 has been deprecated and is disabled by default.* > Note: *MD002 has been deprecated and is disabled by default.*
> [MD041/first-line-heading](#md041---first-line-in-a-file-should-be-a-top-level-heading) > [MD041/first-line-heading](#md041) offers an improved implementation.
offers an improved implementation.
This rule is intended to ensure document headings start at the top level and This rule is intended to ensure document headings start at the top level and
is triggered when the first heading in the document isn't an h1 heading: is triggered when the first heading in the document isn't an h1 heading:
@ -784,8 +783,7 @@ The `lines_above` and `lines_below` parameters can be used to specify a differen
number of blank lines (including 0) above or below each heading. number of blank lines (including 0) above or below each heading.
Note: If `lines_above` or `lines_below` are configured to require more than one Note: If `lines_above` or `lines_below` are configured to require more than one
blank line, [MD012/no-multiple-blanks](#md012---multiple-consecutive-blank-lines) blank line, [MD012/no-multiple-blanks](#md012) should also be customized.
should also be customized.
Rationale: Aside from aesthetic reasons, some parsers, including `kramdown`, will Rationale: Aside from aesthetic reasons, some parsers, including `kramdown`, will
not parse headings that don't have a blank line before, and will parse them as not parse headings that don't have a blank line before, and will parse them as
@ -1993,19 +1991,42 @@ Rationale: Consistent formatting makes it easier to understand a document.
Tags: links Tags: links
Aliases: valid-link-fragments Aliases: link-fragments
This rule is triggered if a link fragment does not correspond to a This rule is triggered when a link fragment does not correspond to a heading
heading within the document: in the document:
```markdown ```markdown
# Title # Title
[Link](#invalid-fragment) [Link](#fragment)
``` ```
To fix this issue, ensure that the heading exists, To fix the issue, change the fragment to reference an existing heading:
here you could replace `#invalid-fragment` by `#title`.
It's not part of the CommonMark specification, ```markdown
for example [GitHub turn headings into links](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links). [Link](#title)
```
Alternatively, an HTML `a` tag with an `id` (or a `name`) attribute defines a
valid anchor:
```markdown
<a id="fragment"></a>
```
Some platforms (e.g., [GitHub][github-section-links]) automatically create HTML
anchors for every heading. This makes it easy to link to different sections in
a document. These internal links can break over time as headings are renamed.
Note: Creating anchors for headings is not part of the CommonMark specification.
[github-section-links]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
<!-- markdownlint-configure-file {
"no-inline-html": {
"allowed_elements": [
"a"
]
}
} -->

View file

@ -18,6 +18,9 @@ const inlineCommentStartRe =
/(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-next-line|configure-file))(?:\s|-->)/ig; /(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-next-line|configure-file))(?:\s|-->)/ig;
module.exports.inlineCommentStartRe = inlineCommentStartRe; module.exports.inlineCommentStartRe = inlineCommentStartRe;
// Regular expression for matching HTML elements
module.exports.htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g;
// Regular expressions for range matching // Regular expressions for range matching
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig; module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig;
module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/; module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
@ -419,7 +422,7 @@ module.exports.forEachHeading = function forEachHeading(params, handler) {
} else if (token.type === "heading_close") { } else if (token.type === "heading_close") {
heading = null; heading = null;
} else if ((token.type === "inline") && heading) { } else if ((token.type === "inline") && heading) {
handler(heading, token.content); handler(heading, token.content, token);
} }
}); });
}; };

View file

@ -3,11 +3,10 @@
"use strict"; "use strict";
const { const {
addError, forEachLine, overlapsAnyRange, unescapeMarkdown addError, forEachLine, htmlElementRe, overlapsAnyRange, unescapeMarkdown
} = require("../helpers"); } = require("../helpers");
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g;
const linkDestinationRe = /]\(\s*$/; const linkDestinationRe = /]\(\s*$/;
// See https://spec.commonmark.org/0.29/#autolinks // See https://spec.commonmark.org/0.29/#autolinks
const emailAddressRe = const emailAddressRe =

View file

@ -2,45 +2,74 @@
"use strict"; "use strict";
const { addError, forEachHeading, filterTokens } = require("../helpers"); const { addError, escapeForRegExp, filterTokens, forEachLine, forEachHeading,
htmlElementRe, overlapsAnyRange } = require("../helpers");
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache");
// Regular expression for identifying HTML anchor names
const identifierRe = /(?:id|name)\s*=\s*['"]?([^'"\s>]+)/iu;
/** /**
* Converts a Markdown heading into an HTML fragment * Converts a Markdown heading into an HTML fragment according to the rules
* according to the rules used by GitHub. * used by GitHub.
* *
* @param {string} string The string to convert. * @param {Object} inline Inline token for heading.
* @returns {string} The converted string. * @returns {string} Fragment string for heading.
*/ */
function convertHeadingToHTMLFragment(string) { function convertHeadingToHTMLFragment(inline) {
return "#" + string const inlineText = inline.children.map((token) => token.content).join("");
return "#" + inlineText
.toLowerCase() .toLowerCase()
.replace(/ /g, "-") .replace(/ /g, "-")
.replace(/[^-_a-z0-9]/g, ""); .replace(/[^-_a-z0-9]/g, "");
} }
module.exports = { module.exports = {
"names": [ "MD051", "valid-link-fragments" ], "names": [ "MD051", "link-fragments" ],
"description": "Link fragments should be valid", "description": "Link fragments should be valid",
"tags": [ "links" ], "tags": [ "links" ],
"function": function MD051(params, onError) { "function": function MD051(params, onError) {
const validLinkFragments = []; const fragments = new Set();
forEachHeading(params, (_heading, content) => { forEachHeading(params, (heading, content, inline) => {
validLinkFragments.push(convertHeadingToHTMLFragment(content)); fragments.add(convertHeadingToHTMLFragment(inline));
}); });
filterTokens(params, "inline", (token) => { const exclusions = codeBlockAndSpanRanges();
token.children.forEach((child) => { forEachLine(lineMetadata(), (line, lineIndex, inCode) => {
const { lineNumber, type, attrs } = child; let match = null;
if (type === "link_open") { // eslint-disable-next-line no-unmodified-loop-condition
const href = attrs.find((attr) => attr[0] === "href"); while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) {
if (href !== undefined && const [ tag, , element ] = match;
href[1].startsWith("#") && if (
!validLinkFragments.includes(href[1]) (element.toLowerCase() === "a") &&
) { !overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length)
const detail = "Link Fragment is invalid"; ) {
addError(onError, lineNumber, detail, href[1]); const idMatch = identifierRe.exec(tag);
if (idMatch) {
fragments.add(`#${idMatch[1]}`);
} }
} }
}); }
});
filterTokens(params, "inline", (token) => {
for (const child of token.children) {
const { attrs, lineNumber, line, type } = child;
if (type === "link_open") {
const href = attrs.find((attr) => attr[0] === "href");
const id = href && href[1];
if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) {
let context = id;
let range = null;
const match = line.match(
new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`)
);
if (match) {
context = match[0];
range = [ match.index + 1, match[0].length ];
}
addError(onError, lineNumber, null, context, range);
}
}
}
}); });
} }
}; };

View file

@ -264,6 +264,6 @@
"style": "consistent" "style": "consistent"
}, },
// MD051/valid-link-fragments - Link fragments should be valid // MD051/link-fragments - Link fragments should be valid
"MD051": true "MD051": true
} }

View file

@ -238,5 +238,5 @@ MD050:
# Strong style should be consistent # Strong style should be consistent
style: "consistent" style: "consistent"
# MD051/valid-link-fragments - Link fragments should be valid # MD051/link-fragments - Link fragments should be valid
MD051: true MD051: true

View file

@ -879,14 +879,12 @@
"$ref": "#/properties/MD050" "$ref": "#/properties/MD050"
}, },
"MD051": { "MD051": {
"description": "MD051/valid-link-fragments - Link fragments should be valid", "description": "MD051/link-fragments - Link fragments should be valid",
"type": "boolean", "type": "boolean",
"default": true "default": true
}, },
"valid-link-fragments": { "link-fragments": {
"description": "MD051/valid-link-fragments - Link fragments should be valid", "$ref": "#/properties/MD051"
"type": "boolean",
"default": true
}, },
"headings": { "headings": {
"description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043", "description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043",

View file

@ -93,4 +93,6 @@ Strong __with__ underscore style
Strong **with** different style {MD050} Strong **with** different style {MD050}
[Missing link fragment](#missing) {MD051}
EOF {MD047} EOF {MD047}

View file

@ -9,5 +9,6 @@
"names": [ "names": [
"markdownlint" "markdownlint"
] ]
} },
"MD051": false
} }

View file

@ -314,31 +314,5 @@
"MD050", "MD050",
"strong-style" "strong-style"
] ]
},
{
"errorContext": "#",
"errorDetail": "Link Fragment is invalid",
"errorRange": null,
"fixInfo": null,
"lineNumber": 5,
"ruleDescription": "Link fragments should be valid",
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md051",
"ruleNames": [
"MD051",
"valid-link-fragments"
]
},
{
"errorContext": "#one",
"errorDetail": "Link Fragment is invalid",
"errorRange": null,
"fixInfo": null,
"lineNumber": 17,
"ruleDescription": "Link fragments should be valid",
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md051",
"ruleNames": [
"MD051",
"valid-link-fragments"
]
} }
] ]

View file

@ -0,0 +1,3 @@
# detailed-results-MD051-MD060
A [link with a missing](#fragment)

View file

@ -0,0 +1,3 @@
# detailed-results-MD051-MD060
A [link with a missing](#fragment)

View file

@ -0,0 +1,18 @@
[
{
"errorContext": "[link with a missing](#fragment)",
"errorDetail": null,
"errorRange": [
3,
32
],
"fixInfo": null,
"lineNumber": 3,
"ruleDescription": "Link fragments should be valid",
"ruleInformation": "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/Rules.md#md051",
"ruleNames": [
"MD051",
"link-fragments"
]
}
]

View file

@ -12,17 +12,17 @@
[text]( <> "title" ) {MD042} [text]( <> "title" ) {MD042}
[text](#) {MD042} {MD051} [text](#) {MD042}
[text]( # ) {MD042} {MD051} [text]( # ) {MD042}
[text](# "title") {MD042} {MD051} [text](# "title") {MD042}
[text]( # "title" ) {MD042} {MD051} [text]( # "title" ) {MD042}
[text][frag] {MD042} {MD051} [text][frag] {MD042}
[text][ frag ] {MD042} {MD051} [text][ frag ] {MD042}
[frag]: # [frag]: #

94
test/link-fragments.md Normal file
View file

@ -0,0 +1,94 @@
# Valid/Invalid Link Fragments
## Valid Fragments
[Valid](#validinvalid-link-fragments)
[Valid](#valid-fragments)
[Valid](#valid-h3-heading)
[Valid](#valid-heading-with-underscores-_)
[Valid](#valid-heading-with-emphasis)
[Valid](#valid-heading-with-quotes--and-double-quotes-)
[Valid](#-valid-heading-with-emoji)
[Valid](#valid-heading--with-emoji-2)
[Valid](#valid-closed-atx-heading)
[Valid](#valid-setext-heading)
[Valid](#namedlink)
[Valid](#idlink)
[Valid](#myident)
[Valid](#HREFandID)
[Valid][goodref]
### Valid H3 Heading
Text
### Valid Heading With Underscores _
Text
### Valid *Heading* With _Emphasis_
Text
### Valid Heading With Quotes ' And Double Quotes "
Text
### 🚀 Valid Heading With Emoji
Text
### Valid Heading 👀 With Emoji 2
Text
### Valid Closed ATX Heading ###
Text
Valid Setext Heading
--------------------
Text
<a name="namedlink"></a>
<a id = idlink></a>
<a id="myident" name="myname"/>
<A href="https://example.com" id="HREFandID">Text</A>
[goodref]: #namedlink
## Invalid Fragments
[Invalid](#invalid-fragment) {MD051}
[Invalid](#myname) {MD051}
[Invalid](#hrefandid) {MD051}
[Invalid][badref] {MD051}
[badref]: #missing
<!-- markdownlint-configure-file {
"emphasis-style": false,
"heading-style": false,
"no-inline-html": false
} -->

View file

@ -35,13 +35,69 @@ async function lintTestRepo(t, globPatterns, configPath, ignoreRes) {
readConfigPromise(configPath, [ jsoncParse, yamlParse ]) readConfigPromise(configPath, [ jsoncParse, yamlParse ])
]).then((globbyAndReadConfigResults) => { ]).then((globbyAndReadConfigResults) => {
const [ files, config ] = globbyAndReadConfigResults; const [ files, config ] = globbyAndReadConfigResults;
const options = {
files,
config
};
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(`${t.title}: Linting ${files.length} files...`); console.log(`${t.title}: Linting ${files.length} files...`);
return markdownlintPromise(options).then((results) => { return markdownlintPromise({
files,
config,
"resultVersion": 3
// }).then((results) => {
// // Cross-check MD051/link-fragments results with markdown-link-check
// const resultFiles = [];
// const detectedErrors = new Set();
// for (const file of Object.keys(results)) {
// const errors =
// results[file].filter((error) => error.ruleNames[0] === "MD051");
// if (errors.length > 0) {
// resultFiles.push(file);
// }
// for (const error of errors) {
// const fragment = error.errorContext.replace(/^.*\((#.*)\)$/, "$1");
// detectedErrors.add(file + fragment);
// }
// }
// const { readFile } = require("fs").promises;
// const markdownLinkCheck = promisify(require("markdown-link-check"));
// const expectedErrors = new Set();
// return Promise.all(
// resultFiles.map((file) => readFile(file, "utf8")
// // @ts-ignore
// .then((markdown) => markdownLinkCheck(markdown, {
// "ignorePatterns": [
// {
// "pattern": "^[^#]"
// }
// ]
// }))
// .then((mlcResults) => {
// const deadResults =
// mlcResults.filter((result) => result.status === "dead");
// for (const link of deadResults.map((result) => result.link)) {
// expectedErrors.add(file + link);
// }
// })
// )
// ).then(() => {
// const extraErrors = [];
// // @ts-ignore
// for (const detectedError of detectedErrors) {
// if (!expectedErrors.has(detectedError)) {
// extraErrors.push(detectedError);
// }
// }
// t.deepEqual(extraErrors, [], "Extra errors");
// const missingErrors = [];
// // @ts-ignore
// for (const expectedError of expectedErrors) {
// if (!detectedErrors.has(expectedError)) {
// missingErrors.push(expectedError);
// }
// }
// t.deepEqual(missingErrors, [], "Missing errors");
// return results;
// });
}).then((results) => {
// Fail if any issues were found (that aren't ignored)
let resultsString = results.toString(); let resultsString = results.toString();
for (const ignoreRe of (ignoreRes || [])) { for (const ignoreRe of (ignoreRes || [])) {
const lengthBefore = resultsString.length; const lengthBefore = resultsString.length;
@ -93,7 +149,8 @@ test("https://github.com/mkdocs/mkdocs", (t) => {
) )
]; ];
const configPath = join(rootDir, ".markdownlintrc"); const configPath = join(rootDir, ".markdownlintrc");
return lintTestRepo(t, globPatterns, configPath); const ignoreRes = [ /^[^:]+: \d+: MD051\/.*$\r?\n?/gm ];
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
}); });
test("https://github.com/mochajs/mocha", (t) => { test("https://github.com/mochajs/mocha", (t) => {
@ -107,14 +164,16 @@ test("https://github.com/mochajs/mocha", (t) => {
join(rootDir, "example/**/*.md") join(rootDir, "example/**/*.md")
]; ];
const configPath = join(rootDir, ".markdownlint.json"); const configPath = join(rootDir, ".markdownlint.json");
return lintTestRepo(t, globPatterns, configPath); const ignoreRes = [ /^[^:]+: \d+: MD051\/.*$\r?\n?/gm ];
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
}); });
test("https://github.com/pi-hole/docs", (t) => { test("https://github.com/pi-hole/docs", (t) => {
const rootDir = "./test-repos/pi-hole-docs"; const rootDir = "./test-repos/pi-hole-docs";
const globPatterns = [ join(rootDir, "**/*.md") ]; const globPatterns = [ join(rootDir, "**/*.md") ];
const configPath = join(rootDir, ".markdownlint.json"); const configPath = join(rootDir, ".markdownlint.json");
return lintTestRepo(t, globPatterns, configPath); const ignoreRes = [ /^[^:]+: \d+: MD051\/.*$\r?\n?/gm ];
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
}); });
test("https://github.com/webhintio/hint", (t) => { test("https://github.com/webhintio/hint", (t) => {
@ -142,7 +201,8 @@ if (existsSync(dotnetDocsDir)) {
const rootDir = dotnetDocsDir; const rootDir = dotnetDocsDir;
const globPatterns = [ join(rootDir, "**/*.md") ]; const globPatterns = [ join(rootDir, "**/*.md") ];
const configPath = join(rootDir, ".markdownlint.json"); const configPath = join(rootDir, ".markdownlint.json");
return lintTestRepo(t, globPatterns, configPath); const ignoreRes = [ /^[^:]+: \d+: MD051\/.*$\r?\n?/gm ];
return lintTestRepo(t, globPatterns, configPath, ignoreRes);
}); });
} }
@ -152,7 +212,7 @@ if (existsSync(v8v8DevDir)) {
const rootDir = v8v8DevDir; const rootDir = v8v8DevDir;
const globPatterns = [ join(rootDir, "src/**/*.md") ]; const globPatterns = [ join(rootDir, "src/**/*.md") ];
const configPath = join(rootDir, ".markdownlint.json"); const configPath = join(rootDir, ".markdownlint.json");
const ignoreRes = [ /^[^:]+: \d+: MD049\/.*$\r?\n?/gm ]; const ignoreRes = [ /^[^:]+: \d+: (MD049|MD051)\/.*$\r?\n?/gm ];
return lintTestRepo(t, globPatterns, configPath, ignoreRes); return lintTestRepo(t, globPatterns, configPath, ignoreRes);
}); });
} }

View file

@ -105,10 +105,7 @@ test.cb("projectFilesInlineConfig", (t) => {
t.plan(2); t.plan(2);
const options = { const options = {
"files": [ "doc/Rules.md" ], "files": [ "doc/Rules.md" ],
"config": { "config": require("../.markdownlint.json")
"no-inline-html": false,
...require("../.markdownlint.json")
}
}; };
markdownlint(options, function callback(err, actual) { markdownlint(options, function callback(err, actual) {
t.falsy(err); t.falsy(err);
@ -491,10 +488,11 @@ test.cb("styleAll", (t) => {
"MD042": [ 81 ], "MD042": [ 81 ],
"MD045": [ 85 ], "MD045": [ 85 ],
"MD046": [ 49, 73, 77 ], "MD046": [ 49, 73, 77 ],
"MD047": [ 96 ], "MD047": [ 98 ],
"MD048": [ 77 ], "MD048": [ 77 ],
"MD049": [ 90 ], "MD049": [ 90 ],
"MD050": [ 94 ] "MD050": [ 94 ],
"MD051": [ 96 ]
} }
}; };
// @ts-ignore // @ts-ignore
@ -536,10 +534,11 @@ test.cb("styleRelaxed", (t) => {
"MD042": [ 81 ], "MD042": [ 81 ],
"MD045": [ 85 ], "MD045": [ 85 ],
"MD046": [ 49, 73, 77 ], "MD046": [ 49, 73, 77 ],
"MD047": [ 96 ], "MD047": [ 98 ],
"MD048": [ 77 ], "MD048": [ 77 ],
"MD049": [ 90 ], "MD049": [ 90 ],
"MD050": [ 94 ] "MD050": [ 94 ],
"MD051": [ 96 ]
} }
}; };
// @ts-ignore // @ts-ignore

View file

@ -88,11 +88,11 @@ Text [link(link](#link`link) text `code`. {MD051}
Text [link)link](#link`link) text `code`. {MD051} Text [link)link](#link`link) text `code`. {MD051}
Text [link](#link[linklink) text `code`. {MD051} Text [link](#link[link`link) text `code`. {MD051}
Text [link](#link[linklink) text `code`. {MD051} Text [link](#link]link`link) text `code`. {MD051}
Text [link](#link[linklink) text `code`. {MD051} Text [link](#link(link`link) text `code`. {MD038}
Text [`link`](xref:custom.link`1) text `code`. Text [`link`](xref:custom.link`1) text `code`.

View file

@ -1,56 +0,0 @@
# Valid/Invalid Link Fragments
## Valid Fragments
[Valid](#validinvalid-link-fragments)
[Valid](#valid-fragments)
[Valid](#valid-h3-heading)
[Valid](#valid-heading-with-underscores-_)
[Valid](#valid-heading-with-quotes--and-double-quotes-)
[Valid](#-valid-heading-with-emoji)
[Valid](#valid-heading--with-emoji-2)
[Valid](#valid-closed-atx-heading)
[Valid](#valid-setex-heading)
### Valid H3 Heading
Text
### Valid Heading With Underscores _
Text
### Valid Heading With Quotes ' And Double Quotes "
Text
### 🚀 Valid Heading With Emoji
Text
### Valid Heading 👀 With Emoji 2
Text
<!-- markdownlint-disable-next-line MD003 -->
### Valid Closed ATX Heading ###
Text
<!-- markdownlint-disable-next-line MD003 -->
Valid Setex Heading
----
Text
## Invalid Fragments
[Invalid](#invalid-fragments-not-exist) {MD051}