Update MD051/link-fragments to identify and fix scenarios where the link fragment has the wrong case (fixes #605).

This commit is contained in:
David Anson 2022-12-16 13:50:38 -08:00
parent d352d4ece1
commit ac8f495ea2
8 changed files with 146 additions and 14 deletions

View file

@ -1247,7 +1247,7 @@ module.exports.fixableRuleNames = [
"MD011", "MD012", "MD014", "MD018", "MD019", "MD020", "MD011", "MD012", "MD014", "MD018", "MD019", "MD020",
"MD021", "MD022", "MD023", "MD026", "MD027", "MD030", "MD021", "MD022", "MD023", "MD026", "MD027", "MD030",
"MD031", "MD032", "MD034", "MD037", "MD038", "MD039", "MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
"MD044", "MD047", "MD049", "MD050", "MD053" "MD044", "MD047", "MD049", "MD050", "MD051", "MD053"
]; ];
module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
module.exports.version = "0.26.2"; module.exports.version = "0.26.2";
@ -4705,7 +4705,7 @@ module.exports = [
"use strict"; "use strict";
// @ts-check // @ts-check
const { addError, escapeForRegExp, filterTokens, forEachInlineChild, forEachHeading, htmlElementRe } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); const { addError, addErrorDetailIf, escapeForRegExp, filterTokens, forEachInlineChild, forEachHeading, htmlElementRe } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js");
// Regular expression for identifying HTML anchor names // Regular expression for identifying HTML anchor names
const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu; const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu;
const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu; const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu;
@ -4767,12 +4767,31 @@ module.exports = {
if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) { if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) {
let context = id; let context = id;
let range = null; let range = null;
let fixInfo = null;
const match = line.match(new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`)); const match = line.match(new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`));
if (match) { if (match) {
context = match[0]; [context] = match;
range = [match.index + 1, match[0].length]; const index = match.index;
const length = context.length;
range = [index + 1, length];
fixInfo = {
"editColumn": index + (length - id.length),
"deleteCount": id.length,
"insertText": null
};
}
const idLower = id.toLowerCase();
const mixedCaseKey = [...fragments.keys()]
.find((key) => idLower === key.toLowerCase());
if (mixedCaseKey) {
(fixInfo || {}).insertText = mixedCaseKey;
addErrorDetailIf(onError, lineNumber, mixedCaseKey, id, undefined, context, range, fixInfo);
}
else {
addError(onError, lineNumber, undefined, context,
// @ts-ignore
range);
} }
addError(onError, lineNumber, undefined, context, range);
} }
}); });
} }

View file

@ -2141,6 +2141,8 @@ Tags: `links`
Aliases: `link-fragments` Aliases: `link-fragments`
Fixable: Most violations can be fixed by tooling
This rule is triggered when a link fragment does not match any of the fragments This rule is triggered when a link fragment does not match any of the fragments
that are automatically generated for headings in a document: that are automatically generated for headings in a document:

View file

@ -4,6 +4,8 @@ Tags: `links`
Aliases: `link-fragments` Aliases: `link-fragments`
Fixable: Most violations can be fixed by tooling
This rule is triggered when a link fragment does not match any of the fragments This rule is triggered when a link fragment does not match any of the fragments
that are automatically generated for headings in a document: that are automatically generated for headings in a document:

View file

@ -8,7 +8,7 @@ module.exports.fixableRuleNames = [
"MD011", "MD012", "MD014", "MD018", "MD019", "MD020", "MD011", "MD012", "MD014", "MD018", "MD019", "MD020",
"MD021", "MD022", "MD023", "MD026", "MD027", "MD030", "MD021", "MD022", "MD023", "MD026", "MD027", "MD030",
"MD031", "MD032", "MD034", "MD037", "MD038", "MD039", "MD031", "MD032", "MD034", "MD037", "MD038", "MD039",
"MD044", "MD047", "MD049", "MD050", "MD053" "MD044", "MD047", "MD049", "MD050", "MD051", "MD053"
]; ];
module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
module.exports.version = "0.26.2"; module.exports.version = "0.26.2";

View file

@ -2,8 +2,8 @@
"use strict"; "use strict";
const { addError, escapeForRegExp, filterTokens, forEachInlineChild, const { addError, addErrorDetailIf, escapeForRegExp, filterTokens,
forEachHeading, htmlElementRe } = require("../helpers"); forEachInlineChild, forEachHeading, htmlElementRe } = require("../helpers");
// Regular expression for identifying HTML anchor names // Regular expression for identifying HTML anchor names
const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu; const idRe = /\sid\s*=\s*['"]?([^'"\s>]+)/iu;
@ -73,14 +73,46 @@ module.exports = {
if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) { if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) {
let context = id; let context = id;
let range = null; let range = null;
let fixInfo = null;
const match = line.match( const match = line.match(
new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`) new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`)
); );
if (match) { if (match) {
context = match[0]; [ context ] = match;
range = [ match.index + 1, match[0].length ]; const index = match.index;
const length = context.length;
range = [ index + 1, length ];
fixInfo = {
"editColumn": index + (length - id.length),
"deleteCount": id.length,
"insertText": null
};
}
const idLower = id.toLowerCase();
const mixedCaseKey = [ ...fragments.keys() ]
.find((key) => idLower === key.toLowerCase());
if (mixedCaseKey) {
(fixInfo || {}).insertText = mixedCaseKey;
addErrorDetailIf(
onError,
lineNumber,
mixedCaseKey,
id,
undefined,
context,
range,
fixInfo
);
} else {
addError(
onError,
lineNumber,
undefined,
context,
// @ts-ignore
range
);
} }
addError(onError, lineNumber, undefined, context, range);
} }
}); });
} }

View file

@ -161,6 +161,16 @@ Text
[badref]: #missing [badref]: #missing
## Inconsistent Case Fragments
[Title](#Valid-Fragments) {MD051}
[ALL CAPS](#NAMEDLINK) {MD051}
[MiXeD][mixedref] {MD051}
[mixedref]: #idLINK
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"emphasis-style": false, "emphasis-style": false,
"heading-style": false, "heading-style": false,

View file

@ -21814,12 +21814,16 @@ Generated by [AVA](https://avajs.dev).
}, },
{ {
errorContext: '[Invalid](#hrefandid)', errorContext: '[Invalid](#hrefandid)',
errorDetail: null, errorDetail: 'Expected: #HREFandID; Actual: #hrefandid',
errorRange: [ errorRange: [
1, 1,
21, 21,
], ],
fixInfo: null, fixInfo: {
deleteCount: 10,
editColumn: 11,
insertText: '#HREFandID',
},
lineNumber: 152, lineNumber: 152,
ruleDescription: 'Link fragments should be valid', ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md', ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
@ -21889,6 +21893,59 @@ Generated by [AVA](https://avajs.dev).
'link-fragments', 'link-fragments',
], ],
}, },
{
errorContext: '[Title](#Valid-Fragments)',
errorDetail: 'Expected: #valid-fragments; Actual: #Valid-Fragments',
errorRange: [
1,
25,
],
fixInfo: {
deleteCount: 16,
editColumn: 9,
insertText: '#valid-fragments',
},
lineNumber: 166,
ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [
'MD051',
'link-fragments',
],
},
{
errorContext: '[ALL CAPS](#NAMEDLINK)',
errorDetail: 'Expected: #namedlink; Actual: #NAMEDLINK',
errorRange: [
1,
22,
],
fixInfo: {
deleteCount: 10,
editColumn: 12,
insertText: '#namedlink',
},
lineNumber: 168,
ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [
'MD051',
'link-fragments',
],
},
{
errorContext: '#idLINK',
errorDetail: 'Expected: #idlink; Actual: #idLINK',
errorRange: null,
fixInfo: null,
lineNumber: 170,
ruleDescription: 'Link fragments should be valid',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md051.md',
ruleNames: [
'MD051',
'link-fragments',
],
},
], ],
fixed: `# Valid/Invalid Link Fragments␊ fixed: `# Valid/Invalid Link Fragments␊
@ -22041,7 +22098,7 @@ Generated by [AVA](https://avajs.dev).
[Invalid](#myname) {MD051}␊ [Invalid](#myname) {MD051}␊
[Invalid](#hrefandid) {MD051}␊ [Invalid](#HREFandID) {MD051}␊
[Invalid](#name-for-other-element) {MD051}␊ [Invalid](#name-for-other-element) {MD051}␊
@ -22053,6 +22110,16 @@ Generated by [AVA](https://avajs.dev).
[badref]: #missing [badref]: #missing
## Inconsistent Case Fragments␊
[Title](#valid-fragments) {MD051}␊
[ALL CAPS](#namedlink) {MD051}␊
[MiXeD][mixedref] {MD051}␊
[mixedref]: #idLINK
<!-- markdownlint-configure-file {␊ <!-- markdownlint-configure-file {␊
"emphasis-style": false,␊ "emphasis-style": false,␊
"heading-style": false,␊ "heading-style": false,␊