markdownlint
Advanced tools
Comparing version 0.25.1 to 0.26.0
@@ -31,2 +31,4 @@ # Contributing | ||
If `some-test.md` needs custom configuration, a `some-test.json` is used to provide a custom `options.config` for that scenario. | ||
Tests run by `markdownlint-test-scenarios.js` use [AVA's snapshot feature](https://github.com/avajs/ava/blob/main/docs/04-snapshot-testing.md). | ||
To update snapshots (for example, after modifying a test file), run `npm run update-snapshots` and include the updated files with the pull request. | ||
@@ -33,0 +35,0 @@ Lint before sending a pull request by running `npm run lint`. |
148
doc/Rules.md
@@ -16,3 +16,3 @@ # Rules | ||
This rule is triggered when you skip heading levels in a markdown document, for | ||
This rule is triggered when you skip heading levels in a Markdown document, for | ||
example: | ||
@@ -335,3 +335,3 @@ | ||
and simpler for editors to implement. Additionally, this can be a compatibility | ||
issue for multi-markdown parsers, which require 4-space indents. More information: | ||
issue for other Markdown parsers, which require 4-space indents. More information: | ||
<https://cirosantilli.com/markdown-style-guide#indentation-of-content-inside-lists> | ||
@@ -401,3 +401,3 @@ and <http://support.markedapp.com/discussions/problems/21-sub-lists-not-indenting>. | ||
Parameters: code_blocks, spaces_per_tab (boolean; default true, number; default 1) | ||
Parameters: code_blocks, ignore_code_languages, spaces_per_tab (boolean; default true, array of string; default empty, number; default 1) | ||
@@ -435,2 +435,8 @@ Fixable: Most violations can be fixed by tooling | ||
When code blocks are scanned (e.g., by default or if `code_blocks` is `true`), | ||
the `ignore_code_languages` parameter can be set to a list of languages that | ||
should be ignored (i.e., hard tabs will be allowed, though not required). This | ||
makes it easier for documents to include code for languages that require hard | ||
tabs. | ||
By default, violations of this rule are fixed by replacing the tab with 1 space | ||
@@ -868,3 +874,3 @@ character. To use a different number of spaces, set the `spaces_per_tab` | ||
Rationale: Some markdown parsers generate anchors for headings based on the | ||
Rationale: Some Markdown parsers generate anchors for headings based on the | ||
heading name; headings with the same content can cause problems with that. | ||
@@ -1024,3 +1030,3 @@ | ||
Rationale: Some markdown parsers will treat two blockquotes separated by one | ||
Rationale: Some Markdown parsers will treat two blockquotes separated by one | ||
or more blank lines as the same blockquote, while others will treat them as | ||
@@ -1306,3 +1312,3 @@ separate blockquotes. | ||
This rule is triggered whenever raw HTML is used in a markdown document: | ||
This rule is triggered whenever raw HTML is used in a Markdown document: | ||
@@ -1313,3 +1319,3 @@ ```markdown | ||
To fix this, use 'pure' markdown instead of including raw HTML: | ||
To fix this, use 'pure' Markdown instead of including raw HTML: | ||
@@ -1322,5 +1328,5 @@ ```markdown | ||
Rationale: Raw HTML is allowed in markdown, but this rule is included for | ||
those who want their documents to only include "pure" markdown, or for those | ||
who are rendering markdown documents in something other than HTML. | ||
Rationale: Raw HTML is allowed in Markdown, but this rule is included for | ||
those who want their documents to only include "pure" Markdown, or for those | ||
who are rendering Markdown documents into something other than HTML. | ||
@@ -1351,3 +1357,3 @@ <a name="md034"></a> | ||
Note: To use a bare URL without it being converted into a link, enclose it in | ||
a code block, otherwise in some markdown parsers it *will* be converted: | ||
a code block, otherwise in some Markdown parsers it *will* be converted: | ||
@@ -1373,3 +1379,3 @@ ```markdown | ||
Rationale: Without angle brackets, the URL isn't converted into a link by many | ||
markdown parsers. | ||
Markdown parsers. | ||
@@ -1443,3 +1449,3 @@ <a name="md035"></a> | ||
To fix this, use markdown headings instead of emphasized text to denote | ||
To fix this, use Markdown headings instead of emphasized text to denote | ||
sections: | ||
@@ -1767,3 +1773,3 @@ | ||
Parameters: names, code_blocks (string array; default `null`, boolean; default `true`) | ||
Parameters: names, code_blocks, html_elements (string array; default `null`, boolean; default `true`, boolean; default `true`) | ||
@@ -1787,3 +1793,5 @@ Fixable: Most violations can be fixed by tooling | ||
Set the `code_blocks` parameter to `false` to disable this rule for code blocks | ||
and spans. | ||
and spans. Set the `html_elements` parameter to `false` to disable this rule | ||
for HTML elements and attributes (such as when using a proper name as part of | ||
a path for `a`/`href` or `img`/`src`). | ||
@@ -2001,1 +2009,111 @@ Rationale: Incorrect capitalization of proper names is usually a mistake. | ||
Rationale: Consistent formatting makes it easier to understand a document. | ||
<a name="md051"></a> | ||
## MD051 - Link fragments should be valid | ||
Tags: links | ||
Aliases: link-fragments | ||
This rule is triggered when a link fragment does not correspond to a heading | ||
in the document: | ||
```markdown | ||
# Title | ||
[Link](#fragment) | ||
``` | ||
To fix the issue, change the fragment to reference an existing heading: | ||
```markdown | ||
[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 | ||
<a name="md052"></a> | ||
## MD052 - Reference links and images should use a label that is defined | ||
Tags: images, links | ||
Aliases: reference-links-images | ||
Links and images in Markdown can provide the link destination or image source | ||
at the time of use or can define it elsewhere and use a label for reference. | ||
The reference format is convenient for keeping paragraph text clutter-free | ||
and makes it easy to reuse the same URL in multiple places. | ||
There are three kinds of reference links and images: | ||
```markdown | ||
Full: [text][label] | ||
Collapsed: [label][] | ||
Shortcut: [label] | ||
Full: ![text][image] | ||
Collapsed: ![image][] | ||
Shortcut: ![image] | ||
[label]: https://example.com/label | ||
[image]: https://example.com/image | ||
``` | ||
A link or image renders correctly when a corresponding label is defined, but | ||
the text displays with brackets if the label is not present. This rule warns | ||
of undefined labels for "full" and "collapsed" reference syntax. | ||
> "Shortcut" syntax is ambiguous and a missing label will not generate an | ||
error. For example, `[shortcut]` could be a shortcut link or the text | ||
"shortcut" in brackets. | ||
<a name="md053"></a> | ||
## MD053 - Link and image reference definitions should be needed | ||
Tags: images, links | ||
Aliases: link-image-reference-definitions | ||
Fixable: Most violations can be fixed by tooling | ||
Links and images in Markdown can provide the link destination or image source | ||
at the time of use or can define it elsewhere and use a label for reference. | ||
The reference format is convenient for keeping paragraph text clutter-free | ||
and makes it easy to reuse the same URL in multiple places. | ||
Because link and image reference definitions are located separately from | ||
where they are used, there are two scenarios where a definition can be | ||
unnecessary: | ||
1. If a label is not referenced by any link or image in a document, that | ||
definition is unused and can be deleted. | ||
1. If a label is defined multiple times in a document, the first definition is | ||
used and the others can be deleted. | ||
This rule considers a reference definition to be used if any link or image | ||
reference has the corresponding label. "Full", "collapsed", and "shortcut" | ||
formats are all supported. | ||
<!-- markdownlint-configure-file { | ||
"no-inline-html": { | ||
"allowed_elements": [ | ||
"a" | ||
] | ||
} | ||
} --> |
@@ -15,8 +15,12 @@ // @ts-check | ||
// Regular expression for matching inline disable/enable comments | ||
const inlineCommentRe = | ||
// Regular expression for matching the start of inline disable/enable comments | ||
const inlineCommentStartRe = | ||
// eslint-disable-next-line max-len | ||
/<!--\s*markdownlint-(?:(?:(disable|enable|capture|restore|disable-file|enable-file|disable-next-line)((?:\s+[a-z0-9_-]+)*))|(?:(configure-file)\s+([\s\S]*?)))\s*-->/ig; | ||
module.exports.inlineCommentRe = inlineCommentRe; | ||
/(<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file|disable-line|disable-next-line|configure-file))(?:\s|-->)/ig; | ||
module.exports.inlineCommentStartRe = inlineCommentStartRe; | ||
// Regular expression for matching HTML elements | ||
const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^`>]*)?)\/?>/g; | ||
module.exports.htmlElementRe = htmlElementRe; | ||
// Regular expressions for range matching | ||
@@ -30,8 +34,9 @@ module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*(?:\/|[^\s\]"'\W])/ig; | ||
// Regular expression for inline links and shortcut reference links | ||
const linkRe = /(\[(?:[^[\]]|\[[^\]]*\])*\])(\(\S*\)|\[\S*\])?/g; | ||
module.exports.linkRe = linkRe; | ||
// Regular expression for reference links (full and collapsed but not shortcut) | ||
const referenceLinkRe = | ||
/!?\\?\[((?:\[[^\]\0]*]|[^\]\0])*)](?:(?:\[([^\]\0]*)\])|[^(]|$)/g; | ||
// Regular expression for link reference definition lines | ||
module.exports.linkReferenceRe = /^ {0,3}\[[^\]]+]:\s.*$/; | ||
// Regular expression for link reference definitions | ||
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])]:/; | ||
module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe; | ||
@@ -65,18 +70,39 @@ // All punctuation characters (normal and full-width) | ||
// Returns true iff the input line is blank (no content) | ||
// Example: Contains nothing, whitespace, or comment (unclosed start/end okay) | ||
module.exports.isBlankLine = function isBlankLine(line) { | ||
// Call to String.replace follows best practices and is not a security check | ||
// False-positive for js/incomplete-multi-character-sanitization | ||
/** | ||
* Returns true iff the input line is blank (contains nothing, whitespace, or | ||
* comments (unclosed start/end comments allowed)). | ||
* | ||
* @param {string} line Input line. | ||
* @returns {boolean} True iff line is blank. | ||
*/ | ||
function isBlankLine(line) { | ||
const startComment = "<!--"; | ||
const endComment = "-->"; | ||
const removeComments = (s) => { | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
const start = s.indexOf(startComment); | ||
const end = s.indexOf(endComment); | ||
if ((end !== -1) && ((start === -1) || (end < start))) { | ||
// Unmatched end comment is first | ||
s = s.slice(end + endComment.length); | ||
} else if ((start !== -1) && (end !== -1)) { | ||
// Start comment is before end comment | ||
s = s.slice(0, start) + s.slice(end + endComment.length); | ||
} else if ((start !== -1) && (end === -1)) { | ||
// Unmatched start comment is last | ||
s = s.slice(0, start); | ||
} else { | ||
// No more comments to remove | ||
return s; | ||
} | ||
} | ||
}; | ||
return ( | ||
!line || | ||
!line.trim() || | ||
!line | ||
.replace(/<!--.*?-->/g, "") | ||
.replace(/<!--.*$/g, "") | ||
.replace(/^.*-->/g, "") | ||
.replace(/>/g, "") | ||
.trim() | ||
!removeComments(line).replace(/>/g, "").trim() | ||
); | ||
}; | ||
} | ||
module.exports.isBlankLine = isBlankLine; | ||
@@ -141,12 +167,6 @@ /** | ||
if (isValid) { | ||
const inlineCommentIndex = text | ||
.slice(i, j + htmlCommentEnd.length) | ||
.search(inlineCommentRe); | ||
// If not a markdownlint inline directive... | ||
if (inlineCommentIndex === -1) { | ||
text = | ||
text.slice(0, i + htmlCommentBegin.length) + | ||
content.replace(/[^\r\n]/g, ".") + | ||
text.slice(j); | ||
} | ||
text = | ||
text.slice(0, i + htmlCommentBegin.length) + | ||
content.replace(/[^\r\n]/g, ".") + | ||
text.slice(j); | ||
} | ||
@@ -260,7 +280,7 @@ } | ||
function filterTokens(params, type, handler) { | ||
params.tokens.forEach(function forToken(token) { | ||
for (const token of params.tokens) { | ||
if (token.type === type) { | ||
handler(token); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -316,7 +336,7 @@ module.exports.filterTokens = filterTokens; | ||
}); | ||
params.tokens.filter(isMathBlock).forEach((token) => { | ||
for (const token of params.tokens.filter(isMathBlock)) { | ||
for (let i = token.map[0]; i < token.map[1]; i++) { | ||
lineMetadata[i][7] = true; | ||
} | ||
}); | ||
} | ||
return lineMetadata; | ||
@@ -334,5 +354,5 @@ }; | ||
function forEachLine(lineMetadata, handler) { | ||
lineMetadata.forEach(function forMetadata(metadata) { | ||
for (const metadata of lineMetadata) { | ||
handler(...metadata); | ||
}); | ||
} | ||
} | ||
@@ -349,3 +369,3 @@ module.exports.forEachLine = forEachLine; | ||
let lastWithMap = { "map": [ 0, 1 ] }; | ||
tokens.forEach((token) => { | ||
for (const token of tokens) { | ||
if ((token.type === "bullet_list_open") || | ||
@@ -383,8 +403,9 @@ (token.type === "ordered_list_open")) { | ||
} else if (token.type === "blockquote_close") { | ||
nesting = nestingStack.pop(); | ||
} else if (token.map) { | ||
nesting = nestingStack.pop() || 0; | ||
} | ||
if (token.map) { | ||
// Track last token with map | ||
lastWithMap = token; | ||
} | ||
}); | ||
} | ||
return flattenedLists; | ||
@@ -397,7 +418,7 @@ }; | ||
filterTokens(params, "inline", function forToken(token) { | ||
token.children.forEach(function forChild(child) { | ||
for (const child of token.children) { | ||
if (child.type === type) { | ||
handler(child, token); | ||
} | ||
}); | ||
} | ||
}); | ||
@@ -409,3 +430,3 @@ }; | ||
let heading = null; | ||
params.tokens.forEach(function forToken(token) { | ||
for (const token of params.tokens) { | ||
if (token.type === "heading_open") { | ||
@@ -416,5 +437,5 @@ heading = token; | ||
} else if ((token.type === "inline") && heading) { | ||
handler(heading, token.content); | ||
handler(heading, token.content, token); | ||
} | ||
}); | ||
} | ||
}; | ||
@@ -431,76 +452,41 @@ | ||
function forEachInlineCodeSpan(input, handler) { | ||
let currentLine = 0; | ||
let currentColumn = 0; | ||
let index = 0; | ||
while (index < input.length) { | ||
let startIndex = -1; | ||
let startLine = -1; | ||
let startColumn = -1; | ||
let tickCount = 0; | ||
let currentTicks = 0; | ||
let state = "normal"; | ||
// Deliberate <= so trailing 0 completes the last span (ex: "text `code`") | ||
// False-positive for js/index-out-of-bounds | ||
for (; index <= input.length; index++) { | ||
const char = input[index]; | ||
// Ignore backticks in link destination | ||
if ((char === "[") && (state === "normal")) { | ||
state = "linkTextOpen"; | ||
} else if ((char === "]") && (state === "linkTextOpen")) { | ||
state = "linkTextClosed"; | ||
} else if ((char === "(") && (state === "linkTextClosed")) { | ||
state = "linkDestinationOpen"; | ||
} else if ( | ||
((char === "(") && (state === "linkDestinationOpen")) || | ||
((char === ")") && (state === "linkDestinationOpen")) || | ||
(state === "linkTextClosed")) { | ||
state = "normal"; | ||
} | ||
// Parse backtick open/close | ||
if ((char === "`") && (state !== "linkDestinationOpen")) { | ||
// Count backticks at start or end of code span | ||
currentTicks++; | ||
if ((startIndex === -1) || (startColumn === -1)) { | ||
startIndex = index + 1; | ||
} | ||
} else { | ||
if ((startIndex >= 0) && | ||
(startColumn >= 0) && | ||
(tickCount === currentTicks)) { | ||
// Found end backticks; invoke callback for code span | ||
const backtickRe = /`+/g; | ||
let match = null; | ||
const backticksLengthAndIndex = []; | ||
while ((match = backtickRe.exec(input)) !== null) { | ||
backticksLengthAndIndex.push([ match[0].length, match.index ]); | ||
} | ||
const newLinesIndex = []; | ||
while ((match = newLineRe.exec(input)) !== null) { | ||
newLinesIndex.push(match.index); | ||
} | ||
let lineIndex = 0; | ||
let lineStartIndex = 0; | ||
let k = 0; | ||
for (let i = 0; i < backticksLengthAndIndex.length - 1; i++) { | ||
const [ startLength, startIndex ] = backticksLengthAndIndex[i]; | ||
if ((startIndex === 0) || (input[startIndex - 1] !== "\\")) { | ||
for (let j = i + 1; j < backticksLengthAndIndex.length; j++) { | ||
const [ endLength, endIndex ] = backticksLengthAndIndex[j]; | ||
if (startLength === endLength) { | ||
for (; k < newLinesIndex.length; k++) { | ||
const newLineIndex = newLinesIndex[k]; | ||
if (startIndex < newLineIndex) { | ||
break; | ||
} | ||
lineIndex++; | ||
lineStartIndex = newLineIndex + 1; | ||
} | ||
const columnIndex = startIndex - lineStartIndex + startLength; | ||
handler( | ||
input.substring(startIndex, index - currentTicks), | ||
startLine, startColumn, tickCount); | ||
startIndex = -1; | ||
startColumn = -1; | ||
} else if ((startIndex >= 0) && (startColumn === -1)) { | ||
// Found start backticks | ||
tickCount = currentTicks; | ||
startLine = currentLine; | ||
startColumn = currentColumn; | ||
input.slice(startIndex + startLength, endIndex), | ||
lineIndex, | ||
columnIndex, | ||
startLength | ||
); | ||
i = j; | ||
break; | ||
} | ||
// Not in backticks | ||
currentTicks = 0; | ||
} | ||
if (char === "\n") { | ||
// On next line | ||
currentLine++; | ||
currentColumn = 0; | ||
} else if ((char === "\\") && | ||
((startIndex === -1) || (startColumn === -1)) && | ||
(input[index + 1] !== "\n")) { | ||
// Escape character outside code, skip next | ||
index++; | ||
currentColumn += 2; | ||
} else { | ||
// On next column | ||
currentColumn++; | ||
} | ||
} | ||
if (startIndex >= 0) { | ||
// Restart loop after unmatched start backticks (ex: "`text``code``") | ||
index = startIndex; | ||
currentLine = startLine; | ||
currentColumn = startColumn; | ||
} | ||
} | ||
@@ -511,2 +497,24 @@ } | ||
/** | ||
* Adds ellipsis to the left/right/middle of the specified text. | ||
* | ||
* @param {string} text Text to ellipsify. | ||
* @param {boolean} [start] True iff the start of the text is important. | ||
* @param {boolean} [end] True iff the end of the text is important. | ||
* @returns {string} Ellipsified text. | ||
*/ | ||
function ellipsify(text, start, end) { | ||
if (text.length <= 30) { | ||
// Nothing to do | ||
} else if (start && end) { | ||
text = text.slice(0, 15) + "..." + text.slice(-15); | ||
} else if (end) { | ||
text = "..." + text.slice(-30); | ||
} else { | ||
text = text.slice(0, 30) + "..."; | ||
} | ||
return text; | ||
} | ||
module.exports.ellipsify = ellipsify; | ||
/** | ||
* Adds a generic error object via the onError callback. | ||
@@ -551,12 +559,4 @@ * | ||
onError, lineNumber, context, left, right, range, fixInfo) { | ||
if (context.length <= 30) { | ||
// Nothing to do | ||
} else if (left && right) { | ||
context = context.substr(0, 15) + "..." + context.substr(-15); | ||
} else if (right) { | ||
context = "..." + context.substr(-30); | ||
} else { | ||
context = context.substr(0, 30) + "..."; | ||
} | ||
addError(onError, lineNumber, null, context, range, fixInfo); | ||
context = ellipsify(context, left, right); | ||
addError(onError, lineNumber, undefined, context, range, fixInfo); | ||
}; | ||
@@ -602,4 +602,23 @@ | ||
/** | ||
* Determines whether the specified range overlaps another range. | ||
* Returns an array of HTML element ranges. | ||
* | ||
* @param {Object} params RuleParams instance. | ||
* @param {Object} lineMetadata Line metadata object. | ||
* @returns {number[][]} Array of ranges (lineIndex, columnIndex, length). | ||
*/ | ||
module.exports.htmlElementRanges = (params, lineMetadata) => { | ||
const exclusions = []; | ||
forEachLine(lineMetadata, (line, lineIndex, inCode) => { | ||
let match = null; | ||
// eslint-disable-next-line no-unmodified-loop-condition | ||
while (!inCode && ((match = htmlElementRe.exec(line)) !== null)) { | ||
exclusions.push([ lineIndex, match.index, match[0].length ]); | ||
} | ||
}); | ||
return exclusions; | ||
}; | ||
/** | ||
* Determines whether the specified range is within another range. | ||
* | ||
* @param {number[][]} ranges Array of ranges (line, index, length). | ||
@@ -609,11 +628,12 @@ * @param {number} lineIndex Line index to check. | ||
* @param {number} length Length to check. | ||
* @returns {boolean} True iff the specified range overlaps. | ||
* @returns {boolean} True iff the specified range is within. | ||
*/ | ||
module.exports.overlapsAnyRange = (ranges, lineIndex, index, length) => ( | ||
const withinAnyRange = (ranges, lineIndex, index, length) => ( | ||
!ranges.every((span) => ( | ||
(lineIndex !== span[0]) || | ||
(index + length < span[1]) || | ||
(index > span[1] + span[2]) | ||
(index < span[1]) || | ||
(index + length > span[1] + span[2]) | ||
)) | ||
); | ||
module.exports.withinAnyRange = withinAnyRange; | ||
@@ -647,2 +667,78 @@ // Returns a range object for a line by applying a RegExp | ||
/** | ||
* Calls the provided function for each link. | ||
* | ||
* @param {string} line Line of Markdown input. | ||
* @param {Function} handler Function taking (index, link, text, destination). | ||
* @returns {void} | ||
*/ | ||
function forEachLink(line, handler) { | ||
// Helper to find matching close symbol for link text/destination | ||
const findClosingSymbol = (index) => { | ||
const begin = line[index]; | ||
const end = (begin === "[") ? "]" : ")"; | ||
let nesting = 0; | ||
let escaping = false; | ||
let pointy = false; | ||
for (let i = index + 1; i < line.length; i++) { | ||
const current = line[i]; | ||
if (current === "\\") { | ||
escaping = !escaping; | ||
} else if (!escaping && (current === begin)) { | ||
nesting++; | ||
} else if (!escaping && (current === end)) { | ||
if (nesting > 0) { | ||
nesting--; | ||
} else if (!pointy) { | ||
// Return index after matching close symbol | ||
return i + 1; | ||
} | ||
} else if ((i === index + 1) && (begin === "(") && (current === "<")) { | ||
pointy = true; | ||
} else if (!escaping && pointy && current === ">") { | ||
pointy = false; | ||
nesting = 0; | ||
} else { | ||
escaping = false; | ||
} | ||
} | ||
// No match found | ||
return -1; | ||
}; | ||
// Scan line for unescaped "[" character | ||
let escaping = false; | ||
for (let i = 0; i < line.length; i++) { | ||
const current = line[i]; | ||
if (current === "\\") { | ||
escaping = !escaping; | ||
} else if (!escaping && (current === "[")) { | ||
// Scan for matching close "]" of link text | ||
const textEnd = findClosingSymbol(i); | ||
if (textEnd !== -1) { | ||
if ((line[textEnd] === "(") || (line[textEnd] === "[")) { | ||
// Scan for matching close ")" or "]" of link destination | ||
const destEnd = findClosingSymbol(textEnd); | ||
if (destEnd !== -1) { | ||
// Call handler with link text and destination | ||
const link = line.slice(i, destEnd); | ||
const text = line.slice(i, textEnd); | ||
const dest = line.slice(textEnd, destEnd); | ||
handler(i, link, text, dest); | ||
i = destEnd; | ||
} | ||
} | ||
if (i < textEnd) { | ||
// Call handler with link text only | ||
const text = line.slice(i, textEnd); | ||
handler(i, text, text); | ||
i = textEnd; | ||
} | ||
} | ||
} else { | ||
escaping = false; | ||
} | ||
} | ||
} | ||
module.exports.forEachLink = forEachLink; | ||
/** | ||
* Returns a list of emphasis markers in code spans and links. | ||
@@ -657,13 +753,12 @@ * | ||
// Search links | ||
lines.forEach((tokenLine, tokenLineIndex) => { | ||
for (const [ tokenLineIndex, tokenLine ] of lines.entries()) { | ||
const inLine = []; | ||
let linkMatch = null; | ||
while ((linkMatch = linkRe.exec(tokenLine))) { | ||
forEachLink(tokenLine, (index, match) => { | ||
let markerMatch = null; | ||
while ((markerMatch = emphasisMarkersRe.exec(linkMatch[0]))) { | ||
inLine.push(linkMatch.index + markerMatch.index); | ||
while ((markerMatch = emphasisMarkersRe.exec(match))) { | ||
inLine.push(index + markerMatch.index); | ||
} | ||
} | ||
}); | ||
byLine[tokenLineIndex] = inLine; | ||
}); | ||
} | ||
// Search code spans | ||
@@ -678,3 +773,3 @@ filterTokens(params, "inline", (token) => { | ||
const codeLines = code.split(newLineRe); | ||
codeLines.forEach((codeLine, codeLineIndex) => { | ||
for (const [ codeLineIndex, codeLine ] of codeLines.entries()) { | ||
const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; | ||
@@ -688,3 +783,3 @@ const inLine = byLine[byLineIndex]; | ||
byLine[byLineIndex] = inLine; | ||
}); | ||
} | ||
} | ||
@@ -699,9 +794,144 @@ ); | ||
/** | ||
* Returns an object with information about reference links and images. | ||
* | ||
* @param {Object} lineMetadata Line metadata object. | ||
* @returns {Object} Reference link/image data. | ||
*/ | ||
function getReferenceLinkImageData(lineMetadata) { | ||
// Initialize return values | ||
const references = new Map(); | ||
const shortcuts = new Set(); | ||
const definitions = new Map(); | ||
const duplicateDefinitions = []; | ||
// Define helper functions | ||
const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); | ||
const exclusions = []; | ||
const excluded = (match) => withinAnyRange( | ||
exclusions, 0, match.index, match[0].length | ||
); | ||
// Convert input to single-line so multi-line links/images are easier | ||
const lineOffsets = []; | ||
let currentOffset = 0; | ||
const contentLines = []; | ||
forEachLine(lineMetadata, (line, lineIndex, inCode) => { | ||
lineOffsets[lineIndex] = currentOffset; | ||
if (!inCode) { | ||
if (line.trim().length === 0) { | ||
// Allow RegExp to detect the end of a block | ||
line = "\0"; | ||
} | ||
contentLines.push(line); | ||
currentOffset += line.length + 1; | ||
} | ||
}); | ||
lineOffsets.push(currentOffset); | ||
const contentLine = contentLines.join(" "); | ||
// Determine single-line exclusions for inline code spans | ||
forEachInlineCodeSpan(contentLine, (code, lineIndex, columnIndex) => { | ||
exclusions.push([ 0, columnIndex, code.length ]); | ||
}); | ||
// Identify all link/image reference definitions | ||
forEachLine(lineMetadata, (line, lineIndex, inCode) => { | ||
if (!inCode) { | ||
const linkReferenceDefinitionMatch = linkReferenceDefinitionRe.exec(line); | ||
if (linkReferenceDefinitionMatch) { | ||
const label = normalizeLabel(linkReferenceDefinitionMatch[1]); | ||
if (definitions.has(label)) { | ||
duplicateDefinitions.push([ label, lineIndex ]); | ||
} else { | ||
definitions.set(label, lineIndex); | ||
} | ||
exclusions.push([ 0, lineOffsets[lineIndex], line.length ]); | ||
} | ||
} | ||
}); | ||
// Identify all link and image references | ||
let lineIndex = 0; | ||
const pendingContents = [ | ||
{ | ||
"content": contentLine, | ||
"contentLineIndex": 0, | ||
"contentIndex": 0, | ||
"topLevel": true | ||
} | ||
]; | ||
let pendingContent = null; | ||
while ((pendingContent = pendingContents.shift())) { | ||
const { content, contentLineIndex, contentIndex, topLevel } = | ||
pendingContent; | ||
let referenceLinkMatch = null; | ||
while ((referenceLinkMatch = referenceLinkRe.exec(content)) !== null) { | ||
const [ matchString, matchText, matchLabel ] = referenceLinkMatch; | ||
if ( | ||
!matchString.startsWith("\\") && | ||
!matchString.startsWith("!\\") && | ||
!matchText.endsWith("\\") && | ||
!(matchLabel || "").endsWith("\\") && | ||
(topLevel || matchString.startsWith("!")) && | ||
!excluded(referenceLinkMatch) | ||
) { | ||
const shortcutLink = (matchLabel === undefined); | ||
const collapsedLink = | ||
(!shortcutLink && (matchLabel.length === 0)); | ||
const label = normalizeLabel( | ||
(shortcutLink || collapsedLink) ? matchText : matchLabel | ||
); | ||
if (label.length > 0) { | ||
if (shortcutLink) { | ||
// Track, but don't validate due to ambiguity: "text [text] text" | ||
shortcuts.add(label); | ||
} else { | ||
const referenceindex = referenceLinkMatch.index; | ||
if (topLevel) { | ||
// Calculate line index | ||
while (lineOffsets[lineIndex + 1] <= referenceindex) { | ||
lineIndex++; | ||
} | ||
} else { | ||
// Use provided line index | ||
lineIndex = contentLineIndex; | ||
} | ||
const referenceIndex = referenceindex + | ||
(topLevel ? -lineOffsets[lineIndex] : contentIndex); | ||
// Track reference and location | ||
const referenceData = references.get(label) || []; | ||
referenceData.push([ | ||
lineIndex, | ||
referenceIndex, | ||
matchString.length | ||
]); | ||
references.set(label, referenceData); | ||
// Check for images embedded in top-level link text | ||
if (!matchString.startsWith("!")) { | ||
pendingContents.push( | ||
{ | ||
"content": matchText, | ||
"contentLineIndex": lineIndex, | ||
"contentIndex": referenceIndex + 1, | ||
"topLevel": false | ||
} | ||
); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return { | ||
references, | ||
shortcuts, | ||
definitions, | ||
duplicateDefinitions | ||
}; | ||
} | ||
module.exports.getReferenceLinkImageData = getReferenceLinkImageData; | ||
/** | ||
* Gets the most common line ending, falling back to the platform default. | ||
* | ||
* @param {string} input Markdown content to analyze. | ||
* @param {string} [platform] Platform identifier (process.platform). | ||
* @param {Object} [os] Node.js "os" module. | ||
* @returns {string} Preferred line ending. | ||
*/ | ||
function getPreferredLineEnding(input, platform) { | ||
function getPreferredLineEnding(input, os) { | ||
let cr = 0; | ||
@@ -711,3 +941,3 @@ let lf = 0; | ||
const endings = input.match(newLineRe) || []; | ||
endings.forEach((ending) => { | ||
for (const ending of endings) { | ||
// eslint-disable-next-line default-case | ||
@@ -725,7 +955,6 @@ switch (ending) { | ||
} | ||
}); | ||
} | ||
let preferredLineEnding = null; | ||
if (!cr && !lf && !crlf) { | ||
preferredLineEnding = | ||
((platform || process.platform) === "win32") ? "\r\n" : "\n"; | ||
preferredLineEnding = (os && os.EOL) || "\n"; | ||
} else if ((lf >= crlf) && (lf >= cr)) { | ||
@@ -763,4 +992,4 @@ preferredLineEnding = "\n"; | ||
* @param {Object} fixInfo RuleOnErrorFixInfo instance. | ||
* @param {string} lineEnding Line ending to use. | ||
* @returns {string} Fixed content. | ||
* @param {string} [lineEnding] Line ending to use. | ||
* @returns {string | null} Fixed content. | ||
*/ | ||
@@ -778,5 +1007,11 @@ function applyFix(line, fixInfo, lineEnding) { | ||
// Applies as many fixes as possible to the input lines | ||
module.exports.applyFixes = function applyFixes(input, errors) { | ||
const lineEnding = getPreferredLineEnding(input); | ||
/** | ||
* Applies as many fixes as possible to Markdown content. | ||
* | ||
* @param {string} input Lines of Markdown content. | ||
* @param {Object[]} errors RuleOnErrorInfo instances. | ||
* @returns {string} Corrected content. | ||
*/ | ||
function applyFixes(input, errors) { | ||
const lineEnding = getPreferredLineEnding(input, require("os")); | ||
const lines = input.split(newLineRe); | ||
@@ -811,4 +1046,6 @@ // Normalize fixInfo objects | ||
// Collapse insert/no-delete and no-insert/delete for same line/column | ||
lastFixInfo = {}; | ||
fixInfos.forEach((fixInfo) => { | ||
lastFixInfo = { | ||
"lineNumber": -1 | ||
}; | ||
for (const fixInfo of fixInfos) { | ||
if ( | ||
@@ -825,3 +1062,3 @@ (fixInfo.lineNumber === lastFixInfo.lineNumber) && | ||
lastFixInfo = fixInfo; | ||
}); | ||
} | ||
fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber); | ||
@@ -831,3 +1068,3 @@ // Apply all (remaining/updated) fixes | ||
let lastEditIndex = -1; | ||
fixInfos.forEach((fixInfo) => { | ||
for (const fixInfo of fixInfos) { | ||
const { lineNumber, editColumn, deleteCount } = fixInfo; | ||
@@ -842,2 +1079,3 @@ const lineIndex = lineNumber - 1; | ||
) { | ||
// @ts-ignore | ||
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding); | ||
@@ -847,6 +1085,7 @@ } | ||
lastEditIndex = editIndex; | ||
}); | ||
} | ||
// Return corrected input | ||
return lines.filter((line) => line !== null).join(lineEnding); | ||
}; | ||
} | ||
module.exports.applyFixes = applyFixes; | ||
@@ -861,24 +1100,29 @@ /** | ||
* @param {string} replace Text to replace with. | ||
* @param {number} [instance] Instance on the line (1-based). | ||
* @returns {Object} Range and fixInfo wrapper. | ||
*/ | ||
function getRangeAndFixInfoIfFound(lines, lineIndex, search, replace) { | ||
let range = null; | ||
let fixInfo = null; | ||
const searchIndex = lines[lineIndex].indexOf(search); | ||
if (searchIndex !== -1) { | ||
const column = searchIndex + 1; | ||
const length = search.length; | ||
range = [ column, length ]; | ||
fixInfo = { | ||
"editColumn": column, | ||
"deleteCount": length, | ||
"insertText": replace | ||
module.exports.getRangeAndFixInfoIfFound = | ||
(lines, lineIndex, search, replace, instance = 1) => { | ||
let range = null; | ||
let fixInfo = null; | ||
let searchIndex = -1; | ||
while (instance > 0) { | ||
searchIndex = lines[lineIndex].indexOf(search, searchIndex + 1); | ||
instance--; | ||
} | ||
if (searchIndex !== -1) { | ||
const column = searchIndex + 1; | ||
const length = search.length; | ||
range = [ column, length ]; | ||
fixInfo = { | ||
"editColumn": column, | ||
"deleteCount": length, | ||
"insertText": replace | ||
}; | ||
} | ||
return { | ||
range, | ||
fixInfo | ||
}; | ||
} | ||
return { | ||
range, | ||
fixInfo | ||
}; | ||
} | ||
module.exports.getRangeAndFixInfoIfFound = getRangeAndFixInfoIfFound; | ||
@@ -910,21 +1154,12 @@ /** | ||
/** | ||
* Calls Object.freeze() on an object and its children. | ||
* Expands a path with a tilde to an absolute path. | ||
* | ||
* @param {Object} obj Object to deep freeze. | ||
* @returns {Object} Object passed to the function. | ||
* @param {string} file Path that may begin with a tilde. | ||
* @param {Object} os Node.js "os" module. | ||
* @returns {string} Absolute path (or original path). | ||
*/ | ||
function deepFreeze(obj) { | ||
const pending = [ obj ]; | ||
let current = null; | ||
while ((current = pending.shift())) { | ||
Object.freeze(current); | ||
for (const name of Object.getOwnPropertyNames(current)) { | ||
const value = current[name]; | ||
if (value && (typeof value === "object")) { | ||
pending.push(value); | ||
} | ||
} | ||
} | ||
return obj; | ||
function expandTildePath(file, os) { | ||
const homedir = os && os.homedir(); | ||
return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file; | ||
} | ||
module.exports.deepFreeze = deepFreeze; | ||
module.exports.expandTildePath = expandTildePath; |
{ | ||
"name": "markdownlint-rule-helpers", | ||
"version": "0.16.0", | ||
"version": "0.17.0", | ||
"description": "A collection of markdownlint helper functions for custom rules", | ||
@@ -14,2 +14,5 @@ "main": "helpers.js", | ||
"bugs": "https://github.com/DavidAnson/markdownlint/issues", | ||
"engines": { | ||
"node": ">=12" | ||
}, | ||
"keywords": [ | ||
@@ -16,0 +19,0 @@ "markdownlint", |
@@ -5,30 +5,20 @@ // @ts-check | ||
let codeBlockAndSpanRanges = null; | ||
module.exports.codeBlockAndSpanRanges = (value) => { | ||
if (value) { | ||
codeBlockAndSpanRanges = value; | ||
} | ||
return codeBlockAndSpanRanges; | ||
}; | ||
const map = new Map(); | ||
let flattenedLists = null; | ||
module.exports.flattenedLists = (value) => { | ||
if (value) { | ||
flattenedLists = value; | ||
module.exports.set = (keyValuePairs) => { | ||
for (const [ key, value ] of Object.entries(keyValuePairs)) { | ||
map.set(key, value); | ||
} | ||
return flattenedLists; | ||
}; | ||
module.exports.clear = () => map.clear(); | ||
let lineMetadata = null; | ||
module.exports.lineMetadata = (value) => { | ||
if (value) { | ||
lineMetadata = value; | ||
} | ||
return lineMetadata; | ||
}; | ||
module.exports.clear = () => { | ||
codeBlockAndSpanRanges = null; | ||
flattenedLists = null; | ||
lineMetadata = null; | ||
}; | ||
module.exports.codeBlockAndSpanRanges = | ||
() => map.get("codeBlockAndSpanRanges"); | ||
module.exports.flattenedLists = | ||
() => map.get("flattenedLists"); | ||
module.exports.htmlElementRanges = | ||
() => map.get("htmlElementRanges"); | ||
module.exports.lineMetadata = | ||
() => map.get("lineMetadata"); | ||
module.exports.referenceLinkImageData = | ||
() => map.get("referenceLinkImageData"); |
@@ -7,2 +7,2 @@ // @ts-check | ||
module.exports.homepage = "https://github.com/DavidAnson/markdownlint"; | ||
module.exports.version = "0.25.1"; | ||
module.exports.version = "0.26.0"; |
@@ -18,16 +18,10 @@ export = markdownlint; | ||
/** | ||
* Files to lint. | ||
* Configuration object. | ||
*/ | ||
files?: string[] | string; | ||
config?: Configuration; | ||
/** | ||
* Strings to lint. | ||
* Configuration parsers. | ||
*/ | ||
strings?: { | ||
[x: string]: string; | ||
}; | ||
configParsers?: ConfigurationParser[]; | ||
/** | ||
* Configuration object. | ||
*/ | ||
config?: Configuration; | ||
/** | ||
* Custom rules. | ||
@@ -37,2 +31,6 @@ */ | ||
/** | ||
* Files to lint. | ||
*/ | ||
files?: string[] | string; | ||
/** | ||
* Front matter pattern. | ||
@@ -42,2 +40,6 @@ */ | ||
/** | ||
* File system implementation. | ||
*/ | ||
fs?: any; | ||
/** | ||
* True to catch exceptions. | ||
@@ -47,2 +49,6 @@ */ | ||
/** | ||
* Additional plugins. | ||
*/ | ||
markdownItPlugins?: Plugin[]; | ||
/** | ||
* True to ignore HTML directives. | ||
@@ -56,9 +62,7 @@ */ | ||
/** | ||
* Additional plugins. | ||
* Strings to lint. | ||
*/ | ||
markdownItPlugins?: Plugin[]; | ||
/** | ||
* File system implementation. | ||
*/ | ||
fs?: any; | ||
strings?: { | ||
[x: string]: string; | ||
}; | ||
}; | ||
@@ -354,3 +358,3 @@ /** | ||
* Configuration object for linting rules. For a detailed schema, see | ||
* {@link ../schema/markdownlint-config-schema.json}. | ||
* {@link ../schema/markdownlint-config-schema.json}. | ||
*/ | ||
@@ -357,0 +361,0 @@ type Configuration = { |
@@ -23,3 +23,3 @@ // @ts-check | ||
* @param {boolean} synchronous Whether to execute synchronously. | ||
* @returns {string} Error message if validation fails. | ||
* @returns {Error | null} Error message if validation fails. | ||
*/ | ||
@@ -33,5 +33,5 @@ function validateRuleList(ruleList, synchronous) { | ||
const allIds = {}; | ||
ruleList.forEach(function forRule(rule, index) { | ||
for (const [ index, rule ] of ruleList.entries()) { | ||
const customIndex = index - rules.length; | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
// eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc | ||
function newError(property) { | ||
@@ -42,3 +42,3 @@ return new Error( | ||
} | ||
[ "names", "tags" ].forEach(function forProperty(property) { | ||
for (const property of [ "names", "tags" ]) { | ||
const value = rule[property]; | ||
@@ -50,7 +50,7 @@ if (!result && | ||
} | ||
}); | ||
[ | ||
} | ||
for (const propertyInfo of [ | ||
[ "description", "string" ], | ||
[ "function", "function" ] | ||
].forEach(function forProperty(propertyInfo) { | ||
]) { | ||
const property = propertyInfo[0]; | ||
@@ -61,3 +61,3 @@ const value = rule[property]; | ||
} | ||
}); | ||
} | ||
if ( | ||
@@ -84,3 +84,3 @@ !result && | ||
if (!result) { | ||
rule.names.forEach(function forName(name) { | ||
for (const name of rule.names) { | ||
const nameUpper = name.toUpperCase(); | ||
@@ -92,4 +92,4 @@ if (!result && (allIds[nameUpper] !== undefined)) { | ||
allIds[nameUpper] = true; | ||
}); | ||
rule.tags.forEach(function forTag(tag) { | ||
} | ||
for (const tag of rule.tags) { | ||
const tagUpper = tag.toUpperCase(); | ||
@@ -101,5 +101,5 @@ if (!result && allIds[tagUpper]) { | ||
allIds[tagUpper] = false; | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
return result; | ||
@@ -122,6 +122,6 @@ } | ||
keys.sort(); | ||
keys.forEach(function forFile(file) { | ||
for (const file of keys) { | ||
const fileResults = lintResults[file]; | ||
if (Array.isArray(fileResults)) { | ||
fileResults.forEach(function forResult(result) { | ||
for (const result of fileResults) { | ||
const ruleMoniker = result.ruleNames ? | ||
@@ -141,15 +141,15 @@ result.ruleNames.join("/") : | ||
"")); | ||
}); | ||
} | ||
} else { | ||
if (!ruleNameToRule) { | ||
ruleNameToRule = {}; | ||
ruleList.forEach(function forRule(rule) { | ||
for (const rule of ruleList) { | ||
const ruleName = rule.names[0].toUpperCase(); | ||
ruleNameToRule[ruleName] = rule; | ||
}); | ||
} | ||
} | ||
Object.keys(fileResults).forEach(function forRule(ruleName) { | ||
for (const [ ruleName, ruleResults ] of Object.entries(fileResults)) { | ||
const rule = ruleNameToRule[ruleName.toUpperCase()]; | ||
const ruleResults = fileResults[ruleName]; | ||
ruleResults.forEach(function forLine(lineNumber) { | ||
for (const lineNumber of ruleResults) { | ||
// @ts-ignore | ||
const nameIndex = Math.min(useAlias ? 1 : 0, rule.names.length - 1); | ||
@@ -159,9 +159,11 @@ const result = | ||
lineNumber + ": " + | ||
// @ts-ignore | ||
rule.names[nameIndex] + " " + | ||
// @ts-ignore | ||
rule.description; | ||
results.push(result); | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
return results.join("\n"); | ||
@@ -202,4 +204,29 @@ } | ||
/** | ||
* Annotate tokens with line/lineNumber. | ||
* Freeze all freeze-able members of a token and its children. | ||
* | ||
* @param {MarkdownItToken} token A markdown-it token. | ||
* @returns {void} | ||
*/ | ||
function freezeToken(token) { | ||
if (token.attrs) { | ||
for (const attr of token.attrs) { | ||
Object.freeze(attr); | ||
} | ||
Object.freeze(token.attrs); | ||
} | ||
if (token.children) { | ||
for (const child of token.children) { | ||
freezeToken(child); | ||
} | ||
Object.freeze(token.children); | ||
} | ||
if (token.map) { | ||
Object.freeze(token.map); | ||
} | ||
Object.freeze(token); | ||
} | ||
/** | ||
* Annotate tokens with line/lineNumber and freeze them. | ||
* | ||
* @param {MarkdownItToken[]} tokens Array of markdown-it tokens. | ||
@@ -209,5 +236,5 @@ * @param {string[]} lines Lines of Markdown content. | ||
*/ | ||
function annotateTokens(tokens, lines) { | ||
function annotateAndFreezeTokens(tokens, lines) { | ||
let trMap = null; | ||
tokens.forEach(function forToken(token) { | ||
for (const token of tokens) { | ||
// Provide missing maps for table content | ||
@@ -235,12 +262,13 @@ if (token.type === "tr_open") { | ||
} | ||
// Annotate children with lineNumber | ||
let lineNumber = token.lineNumber; | ||
} | ||
// Annotate children with lineNumber | ||
if (token.children) { | ||
const codeSpanExtraLines = []; | ||
helpers.forEachInlineCodeSpan( | ||
token.content, | ||
function handleInlineCodeSpan(code) { | ||
if (token.children.some((child) => child.type === "code_inline")) { | ||
helpers.forEachInlineCodeSpan(token.content, (code) => { | ||
codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1); | ||
} | ||
); | ||
(token.children || []).forEach(function forChild(child) { | ||
}); | ||
} | ||
let lineNumber = token.lineNumber; | ||
for (const child of token.children) { | ||
child.lineNumber = lineNumber; | ||
@@ -253,5 +281,7 @@ child.line = lines[lineNumber - 1]; | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
freezeToken(token); | ||
} | ||
Object.freeze(tokens); | ||
} | ||
@@ -268,3 +298,3 @@ | ||
// const tagToRuleNames = {}; | ||
ruleList.forEach(function forRule(rule) { | ||
for (const rule of ruleList) { | ||
const ruleName = rule.names[0].toUpperCase(); | ||
@@ -275,7 +305,7 @@ // The following is useful for updating README.md: | ||
// ")** *" + rule.names.slice(1).join("/") + "* - " + rule.description); | ||
rule.names.forEach(function forName(name) { | ||
for (const name of rule.names) { | ||
const nameUpper = name.toUpperCase(); | ||
aliasToRuleNames[nameUpper] = [ ruleName ]; | ||
}); | ||
rule.tags.forEach(function forTag(tag) { | ||
} | ||
for (const tag of rule.tags) { | ||
const tagUpper = tag.toUpperCase(); | ||
@@ -286,4 +316,4 @@ const ruleNames = aliasToRuleNames[tagUpper] || []; | ||
// tagToRuleNames[tag] = ruleName; | ||
}); | ||
}); | ||
} | ||
} | ||
// The following is useful for updating README.md: | ||
@@ -313,10 +343,10 @@ // Object.keys(tagToRuleNames).sort().forEach(function forTag(tag) { | ||
const effectiveConfig = {}; | ||
ruleList.forEach((rule) => { | ||
for (const rule of ruleList) { | ||
const ruleName = rule.names[0].toUpperCase(); | ||
effectiveConfig[ruleName] = ruleDefault; | ||
}); | ||
deprecatedRuleNames.forEach((ruleName) => { | ||
} | ||
for (const ruleName of deprecatedRuleNames) { | ||
effectiveConfig[ruleName] = false; | ||
}); | ||
Object.keys(config).forEach((key) => { | ||
} | ||
for (const key of Object.keys(config)) { | ||
let value = config[key]; | ||
@@ -331,6 +361,6 @@ if (value) { | ||
const keyUpper = key.toUpperCase(); | ||
(aliasToRuleNames[keyUpper] || []).forEach((ruleName) => { | ||
for (const ruleName of (aliasToRuleNames[keyUpper] || [])) { | ||
effectiveConfig[ruleName] = value; | ||
}); | ||
}); | ||
} | ||
} | ||
return effectiveConfig; | ||
@@ -340,2 +370,35 @@ } | ||
/** | ||
* Parse the content of a configuration file. | ||
* | ||
* @param {string} name Name of the configuration file. | ||
* @param {string} content Configuration content. | ||
* @param {ConfigurationParser[] | null} [parsers] Parsing function(s). | ||
* @returns {Object} Configuration object and error message. | ||
*/ | ||
function parseConfiguration(name, content, parsers) { | ||
let config = null; | ||
let message = ""; | ||
const errors = []; | ||
let index = 0; | ||
// Try each parser | ||
(parsers || [ JSON.parse ]).every((parser) => { | ||
try { | ||
config = parser(content); | ||
} catch (error) { | ||
errors.push(`Parser ${index++}: ${error.message}`); | ||
} | ||
return !config; | ||
}); | ||
// Message if unable to parse | ||
if (!config) { | ||
errors.unshift(`Unable to parse '${name}'`); | ||
message = errors.join("; "); | ||
} | ||
return { | ||
config, | ||
message | ||
}; | ||
} | ||
/** | ||
* Create a mapping of enabled rules per line. | ||
@@ -348,2 +411,3 @@ * | ||
* @param {Configuration} config Configuration object. | ||
* @param {ConfigurationParser[] | null} configParsers Configuration parsers. | ||
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule | ||
@@ -359,2 +423,3 @@ * names. | ||
config, | ||
configParsers, | ||
aliasToRuleNames) { | ||
@@ -369,8 +434,13 @@ // Shared variables | ||
function handleInlineConfig(input, forEachMatch, forEachLine) { | ||
input.forEach((line, lineIndex) => { | ||
for (const [ lineIndex, line ] of input.entries()) { | ||
if (!noInlineConfig) { | ||
let match = null; | ||
while ((match = helpers.inlineCommentRe.exec(line))) { | ||
const action = (match[1] || match[3]).toUpperCase(); | ||
const parameter = match[2] || match[4]; | ||
while ((match = helpers.inlineCommentStartRe.exec(line))) { | ||
const action = match[2].toUpperCase(); | ||
const startIndex = match.index + match[1].length; | ||
const endIndex = line.indexOf("-->", startIndex); | ||
if (endIndex === -1) { | ||
break; | ||
} | ||
const parameter = line.slice(startIndex, endIndex); | ||
forEachMatch(action, parameter, lineIndex + 1); | ||
@@ -382,3 +452,3 @@ } | ||
} | ||
}); | ||
} | ||
} | ||
@@ -388,10 +458,10 @@ // eslint-disable-next-line jsdoc/require-jsdoc | ||
if (action === "CONFIGURE-FILE") { | ||
try { | ||
const json = JSON.parse(parameter); | ||
const { "config": parsed } = parseConfiguration( | ||
"CONFIGURE-FILE", parameter, configParsers | ||
); | ||
if (parsed) { | ||
config = { | ||
...config, | ||
...json | ||
...parsed | ||
}; | ||
} catch { | ||
// Ignore parse errors for inline configuration | ||
} | ||
@@ -404,10 +474,9 @@ } | ||
const enabled = (action.startsWith("ENABLE")); | ||
const items = parameter ? | ||
parameter.trim().toUpperCase().split(/\s+/) : | ||
allRuleNames; | ||
items.forEach((nameUpper) => { | ||
(aliasToRuleNames[nameUpper] || []).forEach((ruleName) => { | ||
const trimmed = parameter && parameter.trim(); | ||
const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames; | ||
for (const nameUpper of items) { | ||
for (const ruleName of (aliasToRuleNames[nameUpper] || [])) { | ||
state[ruleName] = enabled; | ||
}); | ||
}); | ||
} | ||
} | ||
return state; | ||
@@ -436,5 +505,8 @@ } | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function disableNextLine(action, parameter, lineNumber) { | ||
if (action === "DISABLE-NEXT-LINE") { | ||
const nextLineNumber = frontMatterLines.length + lineNumber + 1; | ||
function disableLineNextLine(action, parameter, lineNumber) { | ||
const disableLine = (action === "DISABLE-LINE"); | ||
const disableNextLine = (action === "DISABLE-NEXT-LINE"); | ||
if (disableLine || disableNextLine) { | ||
const nextLineNumber = | ||
frontMatterLines.length + lineNumber + (disableNextLine ? 1 : 0); | ||
enabledRulesPerLineNumber[nextLineNumber] = | ||
@@ -452,11 +524,11 @@ applyEnableDisable( | ||
ruleList, config, aliasToRuleNames); | ||
ruleList.forEach((rule) => { | ||
for (const rule of ruleList) { | ||
const ruleName = rule.names[0].toUpperCase(); | ||
allRuleNames.push(ruleName); | ||
enabledRules[ruleName] = !!effectiveConfig[ruleName]; | ||
}); | ||
} | ||
capturedRules = enabledRules; | ||
handleInlineConfig(lines, enableDisableFile); | ||
handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState); | ||
handleInlineConfig(lines, disableNextLine); | ||
handleInlineConfig(lines, disableLineNextLine); | ||
// Return results | ||
@@ -477,2 +549,3 @@ return { | ||
* @param {Configuration} config Configuration object. | ||
* @param {ConfigurationParser[] | null} configParsers Configuration parsers. | ||
* @param {RegExp} frontMatter Regular expression for front matter. | ||
@@ -491,2 +564,3 @@ * @param {boolean} handleRuleFailures Whether to handle exceptions in rules. | ||
config, | ||
configParsers, | ||
frontMatter, | ||
@@ -501,31 +575,45 @@ handleRuleFailures, | ||
const removeFrontMatterResult = removeFrontMatter(content, frontMatter); | ||
const frontMatterLines = removeFrontMatterResult.frontMatterLines; | ||
// Ignore the content of HTML comments | ||
content = helpers.clearHtmlCommentText(removeFrontMatterResult.content); | ||
// Parse content into tokens and lines | ||
const tokens = md.parse(content, {}); | ||
const lines = content.split(helpers.newLineRe); | ||
annotateTokens(tokens, lines); | ||
const aliasToRuleNames = mapAliasToRuleNames(ruleList); | ||
const { frontMatterLines } = removeFrontMatterResult; | ||
content = removeFrontMatterResult.content; | ||
// Get enabled rules per line (with HTML comments present) | ||
const { effectiveConfig, enabledRulesPerLineNumber } = | ||
getEnabledRulesPerLineNumber( | ||
ruleList, | ||
lines, | ||
content.split(helpers.newLineRe), | ||
frontMatterLines, | ||
noInlineConfig, | ||
config, | ||
aliasToRuleNames | ||
configParsers, | ||
mapAliasToRuleNames(ruleList) | ||
); | ||
// Create parameters for rules | ||
const params = { | ||
"name": helpers.deepFreeze(name), | ||
"tokens": helpers.deepFreeze(tokens), | ||
"lines": helpers.deepFreeze(lines), | ||
"frontMatterLines": helpers.deepFreeze(frontMatterLines) | ||
// Hide the content of HTML comments from rules, etc. | ||
content = helpers.clearHtmlCommentText(content); | ||
// Parse content into tokens and lines | ||
const tokens = md.parse(content, {}); | ||
const lines = content.split(helpers.newLineRe); | ||
annotateAndFreezeTokens(tokens, lines); | ||
// Create (frozen) parameters for rules | ||
const paramsBase = { | ||
name, | ||
tokens, | ||
"lines": Object.freeze(lines), | ||
"frontMatterLines": Object.freeze(frontMatterLines) | ||
}; | ||
cache.lineMetadata(helpers.getLineMetadata(params)); | ||
cache.flattenedLists(helpers.flattenLists(params.tokens)); | ||
cache.codeBlockAndSpanRanges( | ||
helpers.codeBlockAndSpanRanges(params, cache.lineMetadata()) | ||
); | ||
const lineMetadata = | ||
helpers.getLineMetadata(paramsBase); | ||
const codeBlockAndSpanRanges = | ||
helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata); | ||
const flattenedLists = | ||
helpers.flattenLists(paramsBase.tokens); | ||
const htmlElementRanges = | ||
helpers.htmlElementRanges(paramsBase, lineMetadata); | ||
const referenceLinkImageData = | ||
helpers.getReferenceLinkImageData(lineMetadata); | ||
cache.set({ | ||
codeBlockAndSpanRanges, | ||
flattenedLists, | ||
htmlElementRanges, | ||
lineMetadata, | ||
referenceLinkImageData | ||
}); | ||
// Function to run for each rule | ||
@@ -537,3 +625,6 @@ let results = []; | ||
const ruleName = rule.names[0].toUpperCase(); | ||
params.config = effectiveConfig[ruleName]; | ||
const params = { | ||
...paramsBase, | ||
"config": effectiveConfig[ruleName] | ||
}; | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
@@ -733,2 +824,3 @@ function throwError(property) { | ||
* @param {Configuration} config Configuration object. | ||
* @param {ConfigurationParser[] | null} configParsers Configuration parsers. | ||
* @param {RegExp} frontMatter Regular expression for front matter. | ||
@@ -748,2 +840,3 @@ * @param {boolean} handleRuleFailures Whether to handle exceptions in rules. | ||
config, | ||
configParsers, | ||
frontMatter, | ||
@@ -761,4 +854,15 @@ handleRuleFailures, | ||
} | ||
return lintContent(ruleList, file, content, md, config, frontMatter, | ||
handleRuleFailures, noInlineConfig, resultVersion, callback); | ||
return lintContent( | ||
ruleList, | ||
file, | ||
content, | ||
md, | ||
config, | ||
configParsers, | ||
frontMatter, | ||
handleRuleFailures, | ||
noInlineConfig, | ||
resultVersion, | ||
callback | ||
); | ||
} | ||
@@ -789,3 +893,4 @@ // Make a/synchronous call to read file | ||
if (ruleErr) { | ||
return callback(ruleErr); | ||
callback(ruleErr); | ||
return; | ||
} | ||
@@ -801,2 +906,3 @@ let files = []; | ||
const config = options.config || { "default": true }; | ||
const configParsers = options.configParsers || null; | ||
const frontMatter = (options.frontMatter === undefined) ? | ||
@@ -807,9 +913,9 @@ helpers.frontMatterRe : options.frontMatter; | ||
const resultVersion = (options.resultVersion === undefined) ? | ||
2 : options.resultVersion; | ||
3 : options.resultVersion; | ||
const md = markdownIt({ "html": true }); | ||
const markdownItPlugins = options.markdownItPlugins || []; | ||
markdownItPlugins.forEach(function forPlugin(plugin) { | ||
for (const plugin of markdownItPlugins) { | ||
// @ts-ignore | ||
md.use(...plugin); | ||
}); | ||
} | ||
const fs = options.fs || require("fs"); | ||
@@ -846,2 +952,3 @@ const results = newResults(ruleList); | ||
config, | ||
configParsers, | ||
frontMatter, | ||
@@ -855,6 +962,5 @@ handleRuleFailures, | ||
); | ||
} else if (stringsKeys.length > 0) { | ||
} else if ((currentItem = stringsKeys.shift())) { | ||
// Lint next string | ||
concurrency++; | ||
currentItem = stringsKeys.shift(); | ||
lintContent( | ||
@@ -866,2 +972,3 @@ ruleList, | ||
config, | ||
configParsers, | ||
frontMatter, | ||
@@ -897,3 +1004,2 @@ handleRuleFailures, | ||
} | ||
return null; | ||
} | ||
@@ -921,2 +1027,3 @@ | ||
function markdownlintPromise(options) { | ||
// @ts-ignore | ||
return markdownlintPromisify(options); | ||
@@ -932,3 +1039,3 @@ } | ||
function markdownlintSync(options) { | ||
let results = null; | ||
let results = {}; | ||
lintInput(options, true, function callback(error, res) { | ||
@@ -940,2 +1047,3 @@ if (error) { | ||
}); | ||
// @ts-ignore | ||
return results; | ||
@@ -945,35 +1053,2 @@ } | ||
/** | ||
* Parse the content of a configuration file. | ||
* | ||
* @param {string} name Name of the configuration file. | ||
* @param {string} content Configuration content. | ||
* @param {ConfigurationParser[]} parsers Parsing function(s). | ||
* @returns {Object} Configuration object and error message. | ||
*/ | ||
function parseConfiguration(name, content, parsers) { | ||
let config = null; | ||
let message = ""; | ||
const errors = []; | ||
let index = 0; | ||
// Try each parser | ||
(parsers || [ JSON.parse ]).every((parser) => { | ||
try { | ||
config = parser(content); | ||
} catch (error) { | ||
errors.push(`Parser ${index++}: ${error.message}`); | ||
} | ||
return !config; | ||
}); | ||
// Message if unable to parse | ||
if (!config) { | ||
errors.unshift(`Unable to parse '${name}'`); | ||
message = errors.join("; "); | ||
} | ||
return { | ||
config, | ||
message | ||
}; | ||
} | ||
/** | ||
* Resolve referenced "extends" path in a configuration file | ||
@@ -985,3 +1060,3 @@ * using path.resolve() with require.resolve() as a fallback. | ||
* @param {Object} fs File system implementation. | ||
* @param {ResolveConfigExtendsCallback} [callback] Callback (err, result) | ||
* @param {ResolveConfigExtendsCallback} callback Callback (err, result) | ||
* function. | ||
@@ -1056,2 +1131,3 @@ * @returns {void} | ||
callback = parsers; | ||
// @ts-ignore | ||
parsers = null; | ||
@@ -1064,4 +1140,7 @@ } | ||
// Read file | ||
const os = require("os"); | ||
file = helpers.expandTildePath(file, os); | ||
fs.readFile(file, "utf8", (err, content) => { | ||
if (err) { | ||
// @ts-ignore | ||
return callback(err); | ||
@@ -1073,2 +1152,3 @@ } | ||
if (!config) { | ||
// @ts-ignore | ||
return callback(new Error(message)); | ||
@@ -1082,5 +1162,6 @@ } | ||
file, | ||
configExtends, | ||
helpers.expandTildePath(configExtends, os), | ||
fs, | ||
(_, resolvedExtends) => readConfig( | ||
// @ts-ignore | ||
resolvedExtends, | ||
@@ -1091,4 +1172,6 @@ parsers, | ||
if (errr) { | ||
// @ts-ignore | ||
return callback(errr); | ||
} | ||
// @ts-ignore | ||
return callback(null, { | ||
@@ -1102,2 +1185,3 @@ ...extendsConfig, | ||
} | ||
// @ts-ignore | ||
return callback(null, config); | ||
@@ -1136,2 +1220,4 @@ }); | ||
// Read file | ||
const os = require("os"); | ||
file = helpers.expandTildePath(file, os); | ||
const content = fs.readFileSync(file, "utf8"); | ||
@@ -1147,3 +1233,7 @@ // Try to parse file | ||
delete config.extends; | ||
const resolvedExtends = resolveConfigExtendsSync(file, configExtends, fs); | ||
const resolvedExtends = resolveConfigExtendsSync( | ||
file, | ||
helpers.expandTildePath(configExtends, os), | ||
fs | ||
); | ||
return { | ||
@@ -1265,12 +1355,13 @@ ...readConfigSync(resolvedExtends, parsers, fs), | ||
* @typedef {Object} Options | ||
* @property {string[] | string} [files] Files to lint. | ||
* @property {Object.<string, string>} [strings] Strings to lint. | ||
* @property {Configuration} [config] Configuration object. | ||
* @property {ConfigurationParser[]} [configParsers] Configuration parsers. | ||
* @property {Rule[] | Rule} [customRules] Custom rules. | ||
* @property {string[] | string} [files] Files to lint. | ||
* @property {RegExp} [frontMatter] Front matter pattern. | ||
* @property {Object} [fs] File system implementation. | ||
* @property {boolean} [handleRuleFailures] True to catch exceptions. | ||
* @property {Plugin[]} [markdownItPlugins] Additional plugins. | ||
* @property {boolean} [noInlineConfig] True to ignore HTML directives. | ||
* @property {number} [resultVersion] Results object version. | ||
* @property {Plugin[]} [markdownItPlugins] Additional plugins. | ||
* @property {Object} [fs] File system implementation. | ||
* @property {Object.<string, string>} [strings] Strings to lint. | ||
*/ | ||
@@ -1277,0 +1368,0 @@ |
@@ -29,3 +29,3 @@ // @ts-check | ||
const nestingStyles = []; | ||
flattenedLists().forEach((list) => { | ||
for (const list of flattenedLists()) { | ||
if (list.unordered) { | ||
@@ -35,3 +35,3 @@ if (expectedStyle === "consistent") { | ||
} | ||
list.items.forEach((item) => { | ||
for (const item of list.items) { | ||
const itemStyle = unorderedListStyleFor(item); | ||
@@ -74,6 +74,6 @@ if (style === "sublist") { | ||
); | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; |
@@ -14,3 +14,3 @@ // @ts-check | ||
"function": function MD005(params, onError) { | ||
flattenedLists().forEach((list) => { | ||
for (const list of flattenedLists()) { | ||
const expectedIndent = list.indent; | ||
@@ -20,3 +20,3 @@ let expectedEnd = 0; | ||
let endMatching = false; | ||
list.items.forEach((item) => { | ||
for (const item of list.items) { | ||
const { line, lineNumber } = item; | ||
@@ -68,5 +68,5 @@ const actualIndent = indentFor(item); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
}; |
@@ -15,5 +15,5 @@ // @ts-check | ||
"function": function MD006(params, onError) { | ||
flattenedLists().forEach((list) => { | ||
for (const list of flattenedLists()) { | ||
if (list.unordered && !list.nesting && (list.indent !== 0)) { | ||
list.items.forEach((item) => { | ||
for (const item of list.items) { | ||
const { lineNumber, line } = item; | ||
@@ -31,6 +31,6 @@ addErrorDetailIf( | ||
}); | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; |
@@ -17,5 +17,5 @@ // @ts-check | ||
const startIndent = Number(params.config.start_indent || indent); | ||
flattenedLists().forEach((list) => { | ||
for (const list of flattenedLists()) { | ||
if (list.unordered && list.parentsUnordered) { | ||
list.items.forEach((item) => { | ||
for (const item of list.items) { | ||
const { lineNumber, line } = item; | ||
@@ -46,6 +46,6 @@ const expectedIndent = | ||
}); | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
}; |
@@ -5,4 +5,4 @@ // @ts-check | ||
const { addError, filterTokens, forEachInlineCodeSpan, forEachLine, | ||
includesSorted, newLineRe, numericSortAscending } = require("../helpers"); | ||
const { addError, filterTokens, forEachLine, includesSorted, | ||
numericSortAscending } = require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
@@ -36,15 +36,22 @@ | ||
}); | ||
paragraphLineNumbers.sort(numericSortAscending); | ||
const addLineNumberRange = (start, end) => { | ||
for (let i = start; i < end; i++) { | ||
codeInlineLineNumbers.push(i); | ||
} | ||
}; | ||
filterTokens(params, "inline", (token) => { | ||
if (token.children.some((child) => child.type === "code_inline")) { | ||
const tokenLines = params.lines.slice(token.map[0], token.map[1]); | ||
forEachInlineCodeSpan(tokenLines.join("\n"), (code, lineIndex) => { | ||
const codeLineCount = code.split(newLineRe).length; | ||
for (let i = 0; i < codeLineCount; i++) { | ||
codeInlineLineNumbers.push(token.lineNumber + lineIndex + i); | ||
} | ||
}); | ||
let start = 0; | ||
for (const child of token.children) { | ||
if (start > 0) { | ||
addLineNumberRange(start, child.lineNumber); | ||
start = 0; | ||
} | ||
if (child.type === "code_inline") { | ||
start = child.lineNumber; | ||
} | ||
} | ||
if (start > 0) { | ||
addLineNumberRange(start, token.map[1]); | ||
} | ||
}); | ||
codeInlineLineNumbers.sort(numericSortAscending); | ||
} | ||
@@ -72,3 +79,3 @@ const expected = (brSpaces < 2) ? 0 : brSpaces; | ||
expected + "; Actual: " + trailingSpaces, | ||
null, | ||
undefined, | ||
[ column, trailingSpaces ], | ||
@@ -75,0 +82,0 @@ { |
@@ -5,3 +5,4 @@ // @ts-check | ||
const { addError, forEachLine, overlapsAnyRange } = require("../helpers"); | ||
const { addError, filterTokens, forEachLine, withinAnyRange } = | ||
require("../helpers"); | ||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); | ||
@@ -18,2 +19,6 @@ | ||
const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; | ||
const ignoreCodeLanguages = new Set( | ||
(params.config.ignore_code_languages || []) | ||
.map((language) => language.toLowerCase()) | ||
); | ||
const spacesPerTab = params.config.spaces_per_tab; | ||
@@ -24,2 +29,10 @@ const spaceMultiplier = (spacesPerTab === undefined) ? | ||
const exclusions = includeCode ? [] : codeBlockAndSpanRanges(); | ||
filterTokens(params, "fence", (token) => { | ||
const language = token.info.trim().toLowerCase(); | ||
if (ignoreCodeLanguages.has(language)) { | ||
for (let i = token.map[0] + 1; i < token.map[1] - 1; i++) { | ||
exclusions.push([ i, 0, params.lines[i].length ]); | ||
} | ||
} | ||
}); | ||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => { | ||
@@ -32,3 +45,3 @@ if (includeCode || !inCode) { | ||
const length = match[0].length; | ||
if (!overlapsAnyRange(exclusions, lineIndex, index, length)) { | ||
if (!withinAnyRange(exclusions, lineIndex, index, length)) { | ||
addError( | ||
@@ -35,0 +48,0 @@ onError, |
@@ -5,3 +5,3 @@ // @ts-check | ||
const { addError, forEachLine, overlapsAnyRange } = require("../helpers"); | ||
const { addError, forEachLine, withinAnyRange } = require("../helpers"); | ||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); | ||
@@ -28,3 +28,3 @@ | ||
!linkDestination.endsWith("\\") && | ||
!overlapsAnyRange(exclusions, lineIndex, index, length) | ||
!withinAnyRange(exclusions, lineIndex, index, length) | ||
) { | ||
@@ -31,0 +31,0 @@ addError( |
@@ -6,3 +6,3 @@ // @ts-check | ||
const { addErrorDetailIf, filterTokens, forEachHeading, forEachLine, | ||
includesSorted } = require("../helpers"); | ||
includesSorted, linkReferenceDefinitionRe } = require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
@@ -13,3 +13,2 @@ | ||
const longLineRePostfixStrict = "}.+$"; | ||
const labelRe = /^\s*\[.*[^\\]]:/; | ||
const linkOrImageOnlyLineRe = /^[es]*(lT?L|I)[ES]*$/; | ||
@@ -64,7 +63,7 @@ const sternModeRe = /^([#>\s]*\s)?\S*$/; | ||
let childTokenTypes = ""; | ||
token.children.forEach((child) => { | ||
for (const child of token.children) { | ||
if (child.type !== "text" || child.content !== "") { | ||
childTokenTypes += tokenTypeMap[child.type] || "x"; | ||
} | ||
}); | ||
} | ||
if (linkOrImageOnlyLineRe.test(childTokenTypes)) { | ||
@@ -89,3 +88,3 @@ linkOnlyLineNumbers.push(token.lineNumber); | ||
!includesSorted(linkOnlyLineNumbers, lineNumber) && | ||
!labelRe.test(line))) && | ||
!linkReferenceDefinitionRe.test(line))) && | ||
lengthRe.test(line)) { | ||
@@ -92,0 +91,0 @@ addErrorDetailIf( |
@@ -14,3 +14,3 @@ // @ts-check | ||
"function": function MD014(params, onError) { | ||
[ "code_block", "fence" ].forEach((type) => { | ||
for (const type of [ "code_block", "fence" ]) { | ||
filterTokens(params, type, (token) => { | ||
@@ -35,3 +35,3 @@ const margin = (token.type === "fence") ? 1 : 0; | ||
if (allDollars) { | ||
dollarInstances.forEach((instance) => { | ||
for (const instance of dollarInstances) { | ||
const [ i, lineTrim, column, length ] = instance; | ||
@@ -50,7 +50,7 @@ addErrorContext( | ||
); | ||
}); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
}; |
@@ -8,2 +8,4 @@ // @ts-check | ||
const closedAtxRe = /^(#+)([ \t]+)([^ \t]|[^ \t].*[^ \t])([ \t]+)(#+)(\s*)$/; | ||
module.exports = { | ||
@@ -17,3 +19,3 @@ "names": [ "MD021", "no-multiple-space-closed-atx" ], | ||
const { line, lineNumber } = token; | ||
const match = /^(#+)([ \t]+)([^#]+?)([ \t]+)(#+)(\s*)$/.exec(line); | ||
const match = closedAtxRe.exec(line); | ||
if (match) { | ||
@@ -20,0 +22,0 @@ const [ |
@@ -23,3 +23,3 @@ // @ts-check | ||
const { line, lineNumber } = heading; | ||
const trimmedLine = line.replace(/[\s#]*$/, ""); | ||
const trimmedLine = line.replace(/([^\s#])[\s#]+$/, "$1"); | ||
const match = trailingPunctuationRe.exec(trimmedLine); | ||
@@ -26,0 +26,0 @@ if (match && !endOfLineHtmlEntityRe.test(trimmedLine)) { |
@@ -16,3 +16,3 @@ // @ts-check | ||
let listItemNesting = 0; | ||
params.tokens.forEach((token) => { | ||
for (const token of params.tokens) { | ||
const { content, lineNumber, type } = token; | ||
@@ -55,4 +55,4 @@ if (type === "blockquote_open") { | ||
} | ||
}); | ||
} | ||
} | ||
}; |
@@ -14,3 +14,3 @@ // @ts-check | ||
let prevLineNumber = null; | ||
params.tokens.forEach(function forToken(token) { | ||
for (const token of params.tokens) { | ||
if ((token.type === "blockquote_open") && | ||
@@ -29,4 +29,4 @@ (prevToken.type === "blockquote_close")) { | ||
} | ||
}); | ||
} | ||
} | ||
}; |
@@ -21,3 +21,4 @@ // @ts-check | ||
const style = String(params.config.style || "one_or_ordered"); | ||
flattenedLists().filter((list) => !list.unordered).forEach((list) => { | ||
const filteredLists = flattenedLists().filter((list) => !list.unordered); | ||
for (const list of filteredLists) { | ||
const { items } = list; | ||
@@ -53,3 +54,3 @@ let current = 1; | ||
// Validate each list item marker | ||
items.forEach((item) => { | ||
for (const item of items) { | ||
const match = orderedListItemMarkerRe.exec(item.line); | ||
@@ -65,5 +66,5 @@ if (match) { | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
}; |
@@ -17,3 +17,3 @@ // @ts-check | ||
const olMulti = Number(params.config.ol_multi || 1); | ||
flattenedLists().forEach((list) => { | ||
for (const list of flattenedLists()) { | ||
const lineCount = list.lastLineIndex - list.open.map[0]; | ||
@@ -24,3 +24,3 @@ const allSingle = lineCount === list.items.length; | ||
(allSingle ? olSingle : olMulti); | ||
list.items.forEach((item) => { | ||
for (const item of list.items) { | ||
const { line, lineNumber } = item; | ||
@@ -49,5 +49,5 @@ const match = /^[\s>]*\S+(\s*)/.exec(line); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
} | ||
}; |
@@ -8,3 +8,3 @@ // @ts-check | ||
const codeFencePrefixRe = /^(.*?)\s*[`~]/; | ||
const codeFencePrefixRe = /^(.*?)[`~]/; | ||
@@ -28,3 +28,3 @@ module.exports = { | ||
"lineNumber": i + (onTopFence ? 1 : 2), | ||
"insertText": `${prefix}\n` | ||
"insertText": `${prefix.replace(/[^>]/g, " ").trim()}\n` | ||
}; | ||
@@ -31,0 +31,0 @@ addErrorContext( |
@@ -16,3 +16,4 @@ // @ts-check | ||
const { lines } = params; | ||
flattenedLists().filter((list) => !list.nesting).forEach((list) => { | ||
const filteredLists = flattenedLists().filter((list) => !list.nesting); | ||
for (const list of filteredLists) { | ||
const firstIndex = list.open.map[0]; | ||
@@ -49,4 +50,4 @@ if (!isBlankLine(lines[firstIndex - 1])) { | ||
} | ||
}); | ||
} | ||
} | ||
}; |
@@ -6,7 +6,6 @@ // @ts-check | ||
const { | ||
addError, forEachLine, overlapsAnyRange, unescapeMarkdown | ||
addError, forEachLine, htmlElementRe, withinAnyRange, unescapeMarkdown | ||
} = require("../helpers"); | ||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); | ||
const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^>]*)?)\/?>/g; | ||
const linkDestinationRe = /]\(\s*$/; | ||
@@ -36,3 +35,3 @@ // See https://spec.commonmark.org/0.29/#autolinks | ||
!emailAddressRe.test(content) && | ||
!overlapsAnyRange(exclusions, lineIndex, match.index, match[0].length) | ||
!withinAnyRange(exclusions, lineIndex, match.index, match[0].length) | ||
) { | ||
@@ -39,0 +38,0 @@ const prefix = line.substring(0, match.index); |
@@ -14,3 +14,3 @@ // @ts-check | ||
let inLink = false; | ||
token.children.forEach((child) => { | ||
for (const child of token.children) { | ||
const { content, line, lineNumber, type } = child; | ||
@@ -59,5 +59,5 @@ let match = null; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}; |
@@ -12,5 +12,10 @@ // @ts-check | ||
"function": function MD035(params, onError) { | ||
let style = String(params.config.style || "consistent"); | ||
let style = String(params.config.style || "consistent").trim(); | ||
filterTokens(params, "hr", (token) => { | ||
const { lineNumber, markup } = token; | ||
const { line, lineNumber } = token; | ||
let { markup } = token; | ||
const match = line.match(/[_*\-\s\t]+$/); | ||
if (match) { | ||
markup = match[0].trim(); | ||
} | ||
if (style === "consistent") { | ||
@@ -17,0 +22,0 @@ style = markup; |
@@ -52,6 +52,6 @@ // @ts-check | ||
let state = base; | ||
params.tokens.forEach(function forToken(token) { | ||
for (const token of params.tokens) { | ||
state = state(token); | ||
}); | ||
} | ||
} | ||
}; |
@@ -10,4 +10,8 @@ // @ts-check | ||
const rightSpaceRe = /[^`]\s$/; | ||
const singleLeftRightSpaceRe = /^\s(?:\S.*\S|\S)\s$/; | ||
const spaceInsideCodeInline = (token) => ( | ||
(token.type === "code_inline") && | ||
(leftSpaceRe.test(token.content) || rightSpaceRe.test(token.content)) | ||
); | ||
module.exports = { | ||
@@ -19,3 +23,3 @@ "names": [ "MD038", "no-space-in-code" ], | ||
filterTokens(params, "inline", (token) => { | ||
if (token.children.some((child) => child.type === "code_inline")) { | ||
if (token.children.some(spaceInsideCodeInline)) { | ||
const tokenLines = params.lines.slice(token.map[0], token.map[1]); | ||
@@ -38,4 +42,3 @@ forEachInlineCodeSpan( | ||
} | ||
const allowed = singleLeftRightSpaceRe.test(code); | ||
if ((left || right) && !allowed) { | ||
if (left || right) { | ||
const codeLinesRange = codeLines[rangeLineOffset]; | ||
@@ -42,0 +45,0 @@ if (codeLines.length > 1) { |
@@ -21,4 +21,4 @@ // @ts-check | ||
let lineIndex = 0; | ||
children.forEach((child) => { | ||
const { content, type } = child; | ||
for (const child of children) { | ||
const { content, markup, type } = child; | ||
if (type === "link_open") { | ||
@@ -61,7 +61,9 @@ inLink = true; | ||
} else if (inLink) { | ||
linkText += content; | ||
linkText += type.endsWith("_inline") ? | ||
`${markup}${content}${markup}` : | ||
(content || markup); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}; |
@@ -5,7 +5,5 @@ // @ts-check | ||
const { addErrorContext, filterTokens, rangeFromRegExp } = | ||
const { addErrorContext, escapeForRegExp, filterTokens } = | ||
require("../helpers"); | ||
const emptyLinkRe = /\[[^\]]*](?:\((?:#?|(?:<>))\))/; | ||
module.exports = { | ||
@@ -20,17 +18,26 @@ "names": [ "MD042", "no-empty-links" ], | ||
let emptyLink = false; | ||
token.children.forEach(function forChild(child) { | ||
for (const child of token.children) { | ||
if (child.type === "link_open") { | ||
inLink = true; | ||
linkText = ""; | ||
child.attrs.forEach(function forAttr(attr) { | ||
for (const attr of child.attrs) { | ||
if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) { | ||
emptyLink = true; | ||
} | ||
}); | ||
} | ||
} else if (child.type === "link_close") { | ||
inLink = false; | ||
if (emptyLink) { | ||
addErrorContext(onError, child.lineNumber, | ||
"[" + linkText + "]()", null, null, | ||
rangeFromRegExp(child.line, emptyLinkRe)); | ||
let context = `[${linkText}]`; | ||
let range = null; | ||
const match = child.line.match( | ||
new RegExp(`${escapeForRegExp(context)}\\((?:|#|<>)\\)`) | ||
); | ||
if (match) { | ||
context = match[0]; | ||
range = [ match.index + 1, match[0].length ]; | ||
} | ||
addErrorContext( | ||
onError, child.lineNumber, context, null, null, range | ||
); | ||
emptyLink = false; | ||
@@ -41,5 +48,5 @@ } | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
}; |
@@ -16,5 +16,5 @@ // @ts-check | ||
const levels = {}; | ||
[ 1, 2, 3, 4, 5, 6 ].forEach((level) => { | ||
for (const level of [ 1, 2, 3, 4, 5, 6 ]) { | ||
levels["h" + level] = "######".substr(-level); | ||
}); | ||
} | ||
let i = 0; | ||
@@ -21,0 +21,0 @@ let matchAny = false; |
@@ -6,4 +6,6 @@ // @ts-check | ||
const { addErrorDetailIf, bareUrlRe, escapeForRegExp, forEachLine, | ||
overlapsAnyRange, linkRe, linkReferenceRe } = require("../helpers"); | ||
const { codeBlockAndSpanRanges, lineMetadata } = require("./cache"); | ||
forEachLink, withinAnyRange, linkReferenceDefinitionRe } = | ||
require("../helpers"); | ||
const { codeBlockAndSpanRanges, htmlElementRanges, lineMetadata } = | ||
require("./cache"); | ||
@@ -19,6 +21,10 @@ module.exports = { | ||
const codeBlocks = params.config.code_blocks; | ||
const includeCodeBlocks = (codeBlocks === undefined) ? true : !!codeBlocks; | ||
const includeCodeBlocks = | ||
(codeBlocks === undefined) ? true : !!codeBlocks; | ||
const htmlElements = params.config.html_elements; | ||
const includeHtmlElements = | ||
(htmlElements === undefined) ? true : !!htmlElements; | ||
const exclusions = []; | ||
forEachLine(lineMetadata(), (line, lineIndex) => { | ||
if (linkReferenceRe.test(line)) { | ||
if (linkReferenceDefinitionRe.test(line)) { | ||
exclusions.push([ lineIndex, 0, line.length ]); | ||
@@ -30,10 +36,9 @@ } else { | ||
} | ||
while ((match = linkRe.exec(line)) !== null) { | ||
const [ , text, destination ] = match; | ||
forEachLink(line, (index, _, text, destination) => { | ||
if (destination) { | ||
exclusions.push( | ||
[ lineIndex, match.index + text.length, destination.length ] | ||
[ lineIndex, index + text.length, destination.length ] | ||
); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -44,2 +49,5 @@ }); | ||
} | ||
if (!includeHtmlElements) { | ||
exclusions.push(...htmlElementRanges()); | ||
} | ||
for (const name of names) { | ||
@@ -59,3 +67,6 @@ const escapedName = escapeForRegExp(name); | ||
const length = nameMatch.length; | ||
if (!overlapsAnyRange(exclusions, lineIndex, index, length)) { | ||
if ( | ||
!withinAnyRange(exclusions, lineIndex, index, length) && | ||
!names.includes(nameMatch) | ||
) { | ||
addErrorDetailIf( | ||
@@ -62,0 +73,0 @@ onError, |
@@ -18,16 +18,17 @@ // @ts-check | ||
let expectedStyle = String(params.config.style || "consistent"); | ||
params.tokens | ||
.filter((token) => token.type === "code_block" || token.type === "fence") | ||
.forEach((token) => { | ||
const { lineNumber, type } = token; | ||
if (expectedStyle === "consistent") { | ||
expectedStyle = tokenTypeToStyle[type]; | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
expectedStyle, | ||
tokenTypeToStyle[type]); | ||
}); | ||
const codeBlocksAndFences = params.tokens.filter( | ||
(token) => (token.type === "code_block") || (token.type === "fence") | ||
); | ||
for (const token of codeBlocksAndFences) { | ||
const { lineNumber, type } = token; | ||
if (expectedStyle === "consistent") { | ||
expectedStyle = tokenTypeToStyle[type]; | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
expectedStyle, | ||
tokenTypeToStyle[type]); | ||
} | ||
} | ||
}; |
@@ -14,17 +14,16 @@ // @ts-check | ||
let expectedStyle = style; | ||
params.tokens | ||
.filter((token) => token.type === "fence") | ||
.forEach((fenceToken) => { | ||
const { lineNumber, markup } = fenceToken; | ||
if (expectedStyle === "consistent") { | ||
expectedStyle = fencedCodeBlockStyleFor(markup); | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
expectedStyle, | ||
fencedCodeBlockStyleFor(markup) | ||
); | ||
}); | ||
const fenceTokens = params.tokens.filter((token) => token.type === "fence"); | ||
for (const fenceToken of fenceTokens) { | ||
const { lineNumber, markup } = fenceToken; | ||
if (expectedStyle === "consistent") { | ||
expectedStyle = fencedCodeBlockStyleFor(markup); | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
expectedStyle, | ||
fencedCodeBlockStyleFor(markup) | ||
); | ||
} | ||
} | ||
}; |
@@ -52,6 +52,8 @@ // @ts-check | ||
require("./md048"), | ||
require("./md049"), | ||
require("./md050") | ||
...require("./md049-md050"), | ||
require("./md051"), | ||
require("./md052"), | ||
require("./md053") | ||
]; | ||
rules.forEach((rule) => { | ||
for (const rule of rules) { | ||
const name = rule.names[0].toLowerCase(); | ||
@@ -61,3 +63,3 @@ // eslint-disable-next-line dot-notation | ||
new URL(`${homepage}/blob/v${version}/doc/Rules.md#${name}`); | ||
}); | ||
} | ||
module.exports = rules; |
{ | ||
"name": "markdownlint", | ||
"version": "0.25.1", | ||
"version": "0.26.0", | ||
"description": "A Node.js style checker and lint tool for Markdown/CommonMark files.", | ||
"type": "commonjs", | ||
"main": "lib/markdownlint.js", | ||
@@ -19,3 +20,3 @@ "types": "lib/markdownlint.d.ts", | ||
"build-config-schema": "node schema/build-config-schema.js", | ||
"build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", | ||
"build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --module commonjs --resolveJsonModule --target es2015 lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", | ||
"build-demo": "node scripts copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats", | ||
@@ -45,18 +46,20 @@ "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2", | ||
"test-extra": "ava --timeout=5m test/markdownlint-test-extra-parse.js test/markdownlint-test-extra-type.js", | ||
"update-snapshots": "ava --update-snapshots test/markdownlint-test-scenarios.js", | ||
"upgrade": "npx --yes npm-check-updates --upgrade" | ||
}, | ||
"engines": { | ||
"node": ">=12" | ||
"node": ">=14" | ||
}, | ||
"dependencies": { | ||
"markdown-it": "12.3.2" | ||
"markdown-it": "13.0.1" | ||
}, | ||
"devDependencies": { | ||
"ava": "3.15.0", | ||
"c8": "7.10.0", | ||
"eslint": "8.5.0", | ||
"eslint-plugin-jsdoc": "37.4.0", | ||
"ava": "4.3.0", | ||
"c8": "7.11.3", | ||
"eslint": "8.18.0", | ||
"eslint-plugin-es": "4.1.0", | ||
"eslint-plugin-jsdoc": "39.3.3", | ||
"eslint-plugin-node": "11.1.0", | ||
"eslint-plugin-unicorn": "39.0.0", | ||
"globby": "12.0.2", | ||
"eslint-plugin-unicorn": "42.0.0", | ||
"globby": "13.1.2", | ||
"js-yaml": "4.1.0", | ||
@@ -66,13 +69,13 @@ "markdown-it-for-inline": "0.1.1", | ||
"markdown-it-sup": "1.0.0", | ||
"markdown-it-texmath": "0.9.7", | ||
"markdown-it-texmath": "1.0.0", | ||
"markdownlint-rule-github-internal-links": "0.1.0", | ||
"markdownlint-rule-helpers": "0.15.0", | ||
"markdownlint-rule-helpers": "0.16.0", | ||
"npm-run-all": "4.1.5", | ||
"strip-json-comments": "4.0.0", | ||
"toml": "3.0.0", | ||
"ts-loader": "9.2.6", | ||
"ts-loader": "9.3.0", | ||
"tv4": "1.3.0", | ||
"typescript": "4.5.4", | ||
"webpack": "5.65.0", | ||
"webpack-cli": "4.9.1" | ||
"typescript": "4.7.4", | ||
"webpack": "5.73.0", | ||
"webpack-cli": "4.10.0" | ||
}, | ||
@@ -79,0 +82,0 @@ "keywords": [ |
206
README.md
@@ -46,5 +46,5 @@ # markdownlint | ||
* [Sublime Text markdownlint for Sublime Text](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint) | ||
* [linter-node-markdownlint extension for Atom](https://atom.io/packages/linter-node-markdownlint) | ||
* [coc-markdownlint extension for Vim/Neovim](https://github.com/fannheyward/coc-markdownlint) | ||
* Tooling | ||
* [eslint-plugin-markdownlint for the ESLint analyzer](https://github.com/paweldrozd/eslint-plugin-markdownlint) | ||
* [grunt-markdownlint for the Grunt task runner](https://github.com/sagiegurari/grunt-markdownlint) | ||
@@ -110,2 +110,5 @@ * [Cake.Markdownlint addin for Cake build automation system](https://github.com/cake-contrib/Cake.Markdownlint) | ||
* **[MD050](doc/Rules.md#md050)** *strong-style* - Strong style should be consistent | ||
* **[MD051](doc/Rules.md#md051)** *link-fragments* - Link fragments should be valid | ||
* **[MD052](doc/Rules.md#md052)** *reference-links-images* - Reference links and images should use a label that is defined | ||
* **[MD053](doc/Rules.md#md053)** *link-image-reference-definitions* - Link and image reference definitions should be needed | ||
@@ -142,7 +145,7 @@ <!-- markdownlint-restore --> | ||
* **html** - MD033 | ||
* **images** - MD045 | ||
* **images** - MD045, MD052, MD053 | ||
* **indentation** - MD005, MD006, MD007, MD027 | ||
* **language** - MD040 | ||
* **line_length** - MD013 | ||
* **links** - MD011, MD034, MD039, MD042 | ||
* **links** - MD011, MD034, MD039, MD042, MD051, MD052, MD053 | ||
* **ol** - MD029, MD030, MD032 | ||
@@ -173,7 +176,9 @@ * **spaces** - MD018, MD019, MD020, MD021, MD023 | ||
* Enable all rules: `<!-- markdownlint-enable -->` | ||
* Disable all rules for the next line only: | ||
`<!-- markdownlint-disable-next-line -->` | ||
* Disable all rules for the current line: `<!-- markdownlint-disable-line -->` | ||
* Disable all rules for the next line: `<!-- markdownlint-disable-next-line -->` | ||
* Disable one or more rules by name: `<!-- markdownlint-disable MD001 MD005 -->` | ||
* Enable one or more rules by name: `<!-- markdownlint-enable MD001 MD005 -->` | ||
* Disable one or more rules by name for the next line only: | ||
* Disable one or more rules by name for the current line: | ||
`<!-- markdownlint-disable-line MD001 MD005 -->` | ||
* Disable one or more rules by name for the next line: | ||
`<!-- markdownlint-disable-next-line MD001 MD005 -->` | ||
@@ -193,2 +198,8 @@ * Capture the current rule configuration: `<!-- markdownlint-capture -->` | ||
```markdown | ||
deliberate space * in * emphasis <!-- markdownlint-disable-line no-space-in-emphasis --> | ||
``` | ||
Or: | ||
```markdown | ||
<!-- markdownlint-disable no-space-in-emphasis --> | ||
@@ -259,4 +270,7 @@ deliberate space * in * emphasis | ||
These changes apply to the entire file regardless of where the comment | ||
is located. Multiple such comments (if present) are applied top-to-bottom. | ||
These changes apply to the entire file regardless of where the comment is | ||
located. Multiple such comments (if present) are applied top-to-bottom. By | ||
default, content of `markdownlint-configure-file` is assumed to be JSON, but | ||
[`options.configParsers`](#optionsconfigparsers) can be used to support | ||
alternate formats. | ||
@@ -312,56 +326,2 @@ ## API | ||
##### options.customRules | ||
Type: `Array` of `Object` | ||
List of custom rules to include with the default rule set for linting. | ||
Each array element should define a rule. Rules are typically exported | ||
by another package, but can be defined locally. Custom rules are | ||
identified by the | ||
[keyword `markdownlint-rule` on npm](https://www.npmjs.com/search?q=keywords:markdownlint-rule). | ||
Example: | ||
```js | ||
const extraRules = require("extraRules"); | ||
const options = { | ||
"customRules": [ extraRules.one, extraRules.two ] | ||
}; | ||
``` | ||
See [CustomRules.md](doc/CustomRules.md) for details about authoring | ||
custom rules. | ||
##### options.files | ||
Type: `Array` of `String` | ||
List of files to lint. | ||
Each array element should be a single file (via relative or absolute path); | ||
[globbing](https://en.wikipedia.org/wiki/Glob_%28programming%29) is the | ||
caller's responsibility. | ||
Example: `[ "one.md", "dir/two.md" ]` | ||
##### options.strings | ||
Type: `Object` mapping `String` to `String` | ||
Map of identifiers to strings for linting. | ||
When Markdown content is not available as files, it can be passed as | ||
strings. The keys of the `strings` object are used to identify each | ||
input value in the `result` summary. | ||
Example: | ||
```json | ||
{ | ||
"readme": "# README\n...", | ||
"changelog": "# CHANGELOG\n..." | ||
} | ||
``` | ||
##### options.config | ||
@@ -458,2 +418,55 @@ | ||
##### options.configParsers | ||
Type: *Optional* `Array` of `Function` taking (`String`) and returning `Object` | ||
Array of functions to parse the content of `markdownlint-configure-file` blocks. | ||
As shown in the [Configuration](#configuration) section, inline comments can be | ||
used to customize the [configuration object](#optionsconfig) for a document. By | ||
default, the `JSON.parse` built-in is used, but custom parsers can be specified. | ||
Content is passed to each parser function until one returns a value (vs. throwing | ||
an exception). As such, strict parsers should come before flexible ones. | ||
For example: | ||
```js | ||
[ JSON.parse, require("toml").parse, require("js-yaml").load ] | ||
``` | ||
##### options.customRules | ||
Type: `Array` of `Object` | ||
List of custom rules to include with the default rule set for linting. | ||
Each array element should define a rule. Rules are typically exported | ||
by another package, but can be defined locally. Custom rules are | ||
identified by the | ||
[keyword `markdownlint-rule` on npm](https://www.npmjs.com/search?q=keywords:markdownlint-rule). | ||
Example: | ||
```js | ||
const extraRules = require("extraRules"); | ||
const options = { | ||
"customRules": [ extraRules.one, extraRules.two ] | ||
}; | ||
``` | ||
See [CustomRules.md](doc/CustomRules.md) for details about authoring | ||
custom rules. | ||
##### options.files | ||
Type: `Array` of `String` | ||
List of files to lint. | ||
Each array element should be a single file (via relative or absolute path); | ||
[globbing](https://en.wikipedia.org/wiki/Glob_%28programming%29) is the | ||
caller's responsibility. | ||
Example: `[ "one.md", "dir/two.md" ]` | ||
##### options.frontMatter | ||
@@ -489,2 +502,12 @@ | ||
##### options.fs | ||
Type: `Object` implementing the [file system API](https://nodejs.org/api/fs.html) | ||
In advanced scenarios, it may be desirable to bypass the default file system API. | ||
If a custom file system implementation is provided, `markdownlint` will use that | ||
instead of invoking `require("fs")`. | ||
Note: The only methods called are `readFile` and `readFileSync`. | ||
##### options.handleRuleFailures | ||
@@ -505,2 +528,16 @@ | ||
##### options.markdownItPlugins | ||
Type: `Array` of `Array` of `Function` and plugin parameters | ||
Specifies additional [markdown-it plugins](https://www.npmjs.com/search?q=keywords:markdown-it-plugin) | ||
to use when parsing input. Plugins can be used to support additional syntax and | ||
features for advanced scenarios. | ||
Each item in the top-level `Array` should be of the form: | ||
```js | ||
[ require("markdown-it-plugin"), plugin_param_0, plugin_param_1, ... ] | ||
``` | ||
##### options.noInlineConfig | ||
@@ -533,3 +570,3 @@ | ||
includes information about the line number, rule names, description, as well as any | ||
additional detail or context that is available. *This is the default.* | ||
additional detail or context that is available. *This is deprecated.* | ||
@@ -539,28 +576,23 @@ Passing a `resultVersion` of `3` corresponds to the detailed version `2` format | ||
mode, all errors that occur on each line are reported (other versions report only | ||
the first error for each rule). | ||
the first error for each rule). *This is the default.* | ||
##### options.markdownItPlugins | ||
##### options.strings | ||
Type: `Array` of `Array` of `Function` and plugin parameters | ||
Type: `Object` mapping `String` to `String` | ||
Specifies additional [markdown-it plugins](https://www.npmjs.com/search?q=keywords:markdown-it-plugin) | ||
to use when parsing input. Plugins can be used to support additional syntax and | ||
features for advanced scenarios. | ||
Map of identifiers to strings for linting. | ||
Each item in the top-level `Array` should be of the form: | ||
When Markdown content is not available as files, it can be passed as | ||
strings. The keys of the `strings` object are used to identify each | ||
input value in the `result` summary. | ||
```js | ||
[ require("markdown-it-plugin"), plugin_param_0, plugin_param_1, ... ] | ||
Example: | ||
```json | ||
{ | ||
"readme": "# README\n...", | ||
"changelog": "# CHANGELOG\n..." | ||
} | ||
``` | ||
##### options.fs | ||
Type: `Object` implementing the [file system API](https://nodejs.org/api/fs.html) | ||
In advanced scenarios, it may be desirable to bypass the default file system API. | ||
If a custom file system implementation is provided, `markdownlint` will use that | ||
instead of invoking `require("fs")`. | ||
Note: The only methods called are `readFile` and `readFileSync`. | ||
#### callback | ||
@@ -644,3 +676,4 @@ | ||
first, then those of `file` are applied on top (overriding any of the same keys | ||
appearing in the referenced file). | ||
appearing in the referenced file). If either the `file` or `extends` path begins | ||
with the `~` directory, it will act as a placeholder for the home directory. | ||
@@ -995,2 +1028,9 @@ #### parsers | ||
* 0.25.1 - Update dependencies for CVE-2022-21670. | ||
* 0.26.0 - Add MD051/MD052/MD053 for validating link fragments & reference links/images & | ||
link/image reference definitions (MD053 is auto-fixable), improve | ||
MD010/MD031/MD035/MD039/MD042/MD044/MD049/MD050, add `markdownlint-disable-line` | ||
inline comment, support `~` paths in `readConfig/Sync`, add `configParsers` option, | ||
remove support for end-of-life Node version 12, default `resultVersion` to 3, update | ||
browser script to use ES2015, simplify JSON schema, address remaining CodeQL issues, | ||
improve performance, update dependencies. | ||
@@ -997,0 +1037,0 @@ [npm-image]: https://img.shields.io/npm/v/markdownlint.svg |
@@ -29,10 +29,6 @@ { | ||
"heading-increment": { | ||
"description": "MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD001" | ||
}, | ||
"header-increment": { | ||
"description": "MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD001" | ||
}, | ||
@@ -50,2 +46,4 @@ "MD002": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"maximum": 6, | ||
"default": 1 | ||
@@ -57,32 +55,6 @@ } | ||
"first-heading-h1": { | ||
"description": "MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"level": { | ||
"description": "Heading level", | ||
"type": "integer", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD002" | ||
}, | ||
"first-header-h1": { | ||
"description": "MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"level": { | ||
"description": "Heading level", | ||
"type": "integer", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD002" | ||
}, | ||
@@ -114,48 +86,6 @@ "MD003": { | ||
"heading-style": { | ||
"description": "MD003/heading-style/header-style - Heading style", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Heading style", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"atx", | ||
"atx_closed", | ||
"setext", | ||
"setext_with_atx", | ||
"setext_with_atx_closed" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD003" | ||
}, | ||
"header-style": { | ||
"description": "MD003/heading-style/header-style - Heading style", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Heading style", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"atx", | ||
"atx_closed", | ||
"setext", | ||
"setext_with_atx", | ||
"setext_with_atx_closed" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD003" | ||
}, | ||
@@ -186,23 +116,3 @@ "MD004": { | ||
"ul-style": { | ||
"description": "MD004/ul-style - Unordered list style", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "List style", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"asterisk", | ||
"plus", | ||
"dash", | ||
"sublist" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD004" | ||
}, | ||
@@ -215,5 +125,3 @@ "MD005": { | ||
"list-indent": { | ||
"description": "MD005/list-indent - Inconsistent indentation for list items at the same level", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD005" | ||
}, | ||
@@ -226,5 +134,3 @@ "MD006": { | ||
"ul-start-left": { | ||
"description": "MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD006" | ||
}, | ||
@@ -242,2 +148,3 @@ "MD007": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 2 | ||
@@ -253,2 +160,3 @@ }, | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 2 | ||
@@ -260,26 +168,3 @@ } | ||
"ul-indent": { | ||
"description": "MD007/ul-indent - Unordered list indentation", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"indent": { | ||
"description": "Spaces for indent", | ||
"type": "integer", | ||
"default": 2 | ||
}, | ||
"start_indented": { | ||
"description": "Whether to indent the first level of the list", | ||
"type": "boolean", | ||
"default": false | ||
}, | ||
"start_indent": { | ||
"description": "Spaces for first level indent (when start_indented is set)", | ||
"type": "integer", | ||
"default": 2 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD007" | ||
}, | ||
@@ -297,2 +182,3 @@ "MD009": { | ||
"type": "integer", | ||
"minimum": 0, | ||
"default": 2 | ||
@@ -314,26 +200,3 @@ }, | ||
"no-trailing-spaces": { | ||
"description": "MD009/no-trailing-spaces - Trailing spaces", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"br_spaces": { | ||
"description": "Spaces for line break", | ||
"type": "integer", | ||
"default": 2 | ||
}, | ||
"list_item_empty_lines": { | ||
"description": "Allow spaces for empty lines in list items", | ||
"type": "boolean", | ||
"default": false | ||
}, | ||
"strict": { | ||
"description": "Include unnecessary breaks", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD009" | ||
}, | ||
@@ -353,5 +216,14 @@ "MD010": { | ||
}, | ||
"ignore_code_languages": { | ||
"description": "Fenced code languages to ignore", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
}, | ||
"spaces_per_tab": { | ||
"description": "Number of spaces for each hard tab", | ||
"type": "number", | ||
"type": "integer", | ||
"minimum": 0, | ||
"default": 1 | ||
@@ -363,21 +235,3 @@ } | ||
"no-hard-tabs": { | ||
"description": "MD010/no-hard-tabs - Hard tabs", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"code_blocks": { | ||
"description": "Include code blocks", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"spaces_per_tab": { | ||
"description": "Number of spaces for each hard tab", | ||
"type": "number", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD010" | ||
}, | ||
@@ -390,5 +244,3 @@ "MD011": { | ||
"no-reversed-links": { | ||
"description": "MD011/no-reversed-links - Reversed link syntax", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD011" | ||
}, | ||
@@ -406,2 +258,3 @@ "MD012": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 1 | ||
@@ -413,16 +266,3 @@ } | ||
"no-multiple-blanks": { | ||
"description": "MD012/no-multiple-blanks - Multiple consecutive blank lines", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"maximum": { | ||
"description": "Consecutive blank lines", | ||
"type": "integer", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD012" | ||
}, | ||
@@ -440,2 +280,3 @@ "MD013": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 80 | ||
@@ -446,2 +287,3 @@ }, | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 80 | ||
@@ -452,2 +294,3 @@ }, | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 80 | ||
@@ -489,56 +332,3 @@ }, | ||
"line-length": { | ||
"description": "MD013/line-length - Line length", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"line_length": { | ||
"description": "Number of characters", | ||
"type": "integer", | ||
"default": 80 | ||
}, | ||
"heading_line_length": { | ||
"description": "Number of characters for headings", | ||
"type": "integer", | ||
"default": 80 | ||
}, | ||
"code_block_line_length": { | ||
"description": "Number of characters for code blocks", | ||
"type": "integer", | ||
"default": 80 | ||
}, | ||
"code_blocks": { | ||
"description": "Include code blocks", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"tables": { | ||
"description": "Include tables", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"headings": { | ||
"description": "Include headings", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"headers": { | ||
"description": "Include headings", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"strict": { | ||
"description": "Strict length checking", | ||
"type": "boolean", | ||
"default": false | ||
}, | ||
"stern": { | ||
"description": "Stern length checking", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD013" | ||
}, | ||
@@ -551,5 +341,3 @@ "MD014": { | ||
"commands-show-output": { | ||
"description": "MD014/commands-show-output - Dollar signs used before commands without showing output", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD014" | ||
}, | ||
@@ -562,5 +350,3 @@ "MD018": { | ||
"no-missing-space-atx": { | ||
"description": "MD018/no-missing-space-atx - No space after hash on atx style heading", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD018" | ||
}, | ||
@@ -573,5 +359,3 @@ "MD019": { | ||
"no-multiple-space-atx": { | ||
"description": "MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD019" | ||
}, | ||
@@ -584,5 +368,3 @@ "MD020": { | ||
"no-missing-space-closed-atx": { | ||
"description": "MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD020" | ||
}, | ||
@@ -595,5 +377,3 @@ "MD021": { | ||
"no-multiple-space-closed-atx": { | ||
"description": "MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD021" | ||
}, | ||
@@ -611,2 +391,3 @@ "MD022": { | ||
"type": "integer", | ||
"minimum": 0, | ||
"default": 1 | ||
@@ -617,2 +398,3 @@ }, | ||
"type": "integer", | ||
"minimum": 0, | ||
"default": 1 | ||
@@ -624,42 +406,6 @@ } | ||
"blanks-around-headings": { | ||
"description": "MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"lines_above": { | ||
"description": "Blank lines above heading", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"lines_below": { | ||
"description": "Blank lines below heading", | ||
"type": "integer", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD022" | ||
}, | ||
"blanks-around-headers": { | ||
"description": "MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"lines_above": { | ||
"description": "Blank lines above heading", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"lines_below": { | ||
"description": "Blank lines below heading", | ||
"type": "integer", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD022" | ||
}, | ||
@@ -672,10 +418,6 @@ "MD023": { | ||
"heading-start-left": { | ||
"description": "MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD023" | ||
}, | ||
"header-start-left": { | ||
"description": "MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD023" | ||
}, | ||
@@ -704,42 +446,6 @@ "MD024": { | ||
"no-duplicate-heading": { | ||
"description": "MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"allow_different_nesting": { | ||
"description": "Only check sibling headings", | ||
"type": "boolean", | ||
"default": false | ||
}, | ||
"siblings_only": { | ||
"description": "Only check sibling headings", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD024" | ||
}, | ||
"no-duplicate-header": { | ||
"description": "MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"allow_different_nesting": { | ||
"description": "Only check sibling headings", | ||
"type": "boolean", | ||
"default": false | ||
}, | ||
"siblings_only": { | ||
"description": "Only check sibling headings", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD024" | ||
}, | ||
@@ -757,2 +463,4 @@ "MD025": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"maximum": 6, | ||
"default": 1 | ||
@@ -769,42 +477,6 @@ }, | ||
"single-title": { | ||
"description": "MD025/single-title/single-h1 - Multiple top-level headings in the same document", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"level": { | ||
"description": "Heading level", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"front_matter_title": { | ||
"description": "RegExp for matching title in front matter", | ||
"type": "string", | ||
"default": "^\\s*title\\s*[:=]" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD025" | ||
}, | ||
"single-h1": { | ||
"description": "MD025/single-title/single-h1 - Multiple top-level headings in the same document", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"level": { | ||
"description": "Heading level", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"front_matter_title": { | ||
"description": "RegExp for matching title in front matter", | ||
"type": "string", | ||
"default": "^\\s*title\\s*[:=]" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD025" | ||
}, | ||
@@ -828,16 +500,3 @@ "MD026": { | ||
"no-trailing-punctuation": { | ||
"description": "MD026/no-trailing-punctuation - Trailing punctuation in heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"punctuation": { | ||
"description": "Punctuation characters", | ||
"type": "string", | ||
"default": ".,;:!。,;:!" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD026" | ||
}, | ||
@@ -850,5 +509,3 @@ "MD027": { | ||
"no-multiple-space-blockquote": { | ||
"description": "MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD027" | ||
}, | ||
@@ -861,5 +518,3 @@ "MD028": { | ||
"no-blanks-blockquote": { | ||
"description": "MD028/no-blanks-blockquote - Blank line inside blockquote", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD028" | ||
}, | ||
@@ -889,22 +544,3 @@ "MD029": { | ||
"ol-prefix": { | ||
"description": "MD029/ol-prefix - Ordered list item prefix", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "List style", | ||
"type": "string", | ||
"enum": [ | ||
"one", | ||
"ordered", | ||
"one_or_ordered", | ||
"zero" | ||
], | ||
"default": "one_or_ordered" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD029" | ||
}, | ||
@@ -922,2 +558,3 @@ "MD030": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 1 | ||
@@ -928,2 +565,3 @@ }, | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 1 | ||
@@ -934,2 +572,3 @@ }, | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 1 | ||
@@ -940,2 +579,3 @@ }, | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 1 | ||
@@ -947,31 +587,3 @@ } | ||
"list-marker-space": { | ||
"description": "MD030/list-marker-space - Spaces after list markers", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"ul_single": { | ||
"description": "Spaces for single-line unordered list items", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"ol_single": { | ||
"description": "Spaces for single-line ordered list items", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"ul_multi": { | ||
"description": "Spaces for multi-line unordered list items", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"ol_multi": { | ||
"description": "Spaces for multi-line ordered list items", | ||
"type": "integer", | ||
"default": 1 | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD030" | ||
}, | ||
@@ -995,16 +607,3 @@ "MD031": { | ||
"blanks-around-fences": { | ||
"description": "MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"list_items": { | ||
"description": "Include list items", | ||
"type": "boolean", | ||
"default": true | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD031" | ||
}, | ||
@@ -1017,5 +616,3 @@ "MD032": { | ||
"blanks-around-lists": { | ||
"description": "MD032/blanks-around-lists - Lists should be surrounded by blank lines", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD032" | ||
}, | ||
@@ -1042,19 +639,3 @@ "MD033": { | ||
"no-inline-html": { | ||
"description": "MD033/no-inline-html - Inline HTML", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"allowed_elements": { | ||
"description": "Allowed elements", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD033" | ||
}, | ||
@@ -1067,5 +648,3 @@ "MD034": { | ||
"no-bare-urls": { | ||
"description": "MD034/no-bare-urls - Bare URL used", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD034" | ||
}, | ||
@@ -1089,16 +668,3 @@ "MD035": { | ||
"hr-style": { | ||
"description": "MD035/hr-style - Horizontal rule style", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Horizontal rule style", | ||
"type": "string", | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD035" | ||
}, | ||
@@ -1122,32 +688,6 @@ "MD036": { | ||
"no-emphasis-as-heading": { | ||
"description": "MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"punctuation": { | ||
"description": "Punctuation characters", | ||
"type": "string", | ||
"default": ".,;:!?。,;:!?" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD036" | ||
}, | ||
"no-emphasis-as-header": { | ||
"description": "MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"punctuation": { | ||
"description": "Punctuation characters", | ||
"type": "string", | ||
"default": ".,;:!?。,;:!?" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD036" | ||
}, | ||
@@ -1160,5 +700,3 @@ "MD037": { | ||
"no-space-in-emphasis": { | ||
"description": "MD037/no-space-in-emphasis - Spaces inside emphasis markers", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD037" | ||
}, | ||
@@ -1171,5 +709,3 @@ "MD038": { | ||
"no-space-in-code": { | ||
"description": "MD038/no-space-in-code - Spaces inside code span elements", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD038" | ||
}, | ||
@@ -1182,5 +718,3 @@ "MD039": { | ||
"no-space-in-links": { | ||
"description": "MD039/no-space-in-links - Spaces inside link text", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD039" | ||
}, | ||
@@ -1193,5 +727,3 @@ "MD040": { | ||
"fenced-code-language": { | ||
"description": "MD040/fenced-code-language - Fenced code blocks should have a language specified", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD040" | ||
}, | ||
@@ -1209,2 +741,4 @@ "MD041": { | ||
"type": "integer", | ||
"minimum": 1, | ||
"maximum": 6, | ||
"default": 1 | ||
@@ -1221,42 +755,6 @@ }, | ||
"first-line-heading": { | ||
"description": "MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"level": { | ||
"description": "Heading level", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"front_matter_title": { | ||
"description": "RegExp for matching title in front matter", | ||
"type": "string", | ||
"default": "^\\s*title\\s*[:=]" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD041" | ||
}, | ||
"first-line-h1": { | ||
"description": "MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"level": { | ||
"description": "Heading level", | ||
"type": "integer", | ||
"default": 1 | ||
}, | ||
"front_matter_title": { | ||
"description": "RegExp for matching title in front matter", | ||
"type": "string", | ||
"default": "^\\s*title\\s*[:=]" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD041" | ||
}, | ||
@@ -1269,5 +767,3 @@ "MD042": { | ||
"no-empty-links": { | ||
"description": "MD042/no-empty-links - No empty links", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD042" | ||
}, | ||
@@ -1286,3 +782,4 @@ "MD043": { | ||
"items": { | ||
"type": "string" | ||
"type": "string", | ||
"pattern": "^(\\*|\\+|#{1,6} .*)$" | ||
}, | ||
@@ -1295,3 +792,4 @@ "default": [] | ||
"items": { | ||
"type": "string" | ||
"type": "string", | ||
"pattern": "^(\\*|\\+|#{1,6} .*)$" | ||
}, | ||
@@ -1304,54 +802,6 @@ "default": [] | ||
"required-headings": { | ||
"description": "MD043/required-headings/required-headers - Required heading structure", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"headings": { | ||
"description": "List of headings", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
}, | ||
"headers": { | ||
"description": "List of headings", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD043" | ||
}, | ||
"required-headers": { | ||
"description": "MD043/required-headings/required-headers - Required heading structure", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"headings": { | ||
"description": "List of headings", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
}, | ||
"headers": { | ||
"description": "List of headings", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD043" | ||
}, | ||
@@ -1378,24 +828,5 @@ "MD044": { | ||
"default": true | ||
} | ||
}, | ||
"additionalProperties": false | ||
}, | ||
"proper-names": { | ||
"description": "MD044/proper-names - Proper names should have the correct capitalization", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"names": { | ||
"description": "List of proper names", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"default": [] | ||
}, | ||
"code_blocks": { | ||
"description": "Include code blocks", | ||
"html_elements": { | ||
"description": "Include HTML elements", | ||
"type": "boolean", | ||
@@ -1407,2 +838,5 @@ "default": true | ||
}, | ||
"proper-names": { | ||
"$ref": "#/properties/MD044" | ||
}, | ||
"MD045": { | ||
@@ -1414,5 +848,3 @@ "description": "MD045/no-alt-text - Images should have alternate text (alt text)", | ||
"no-alt-text": { | ||
"description": "MD045/no-alt-text - Images should have alternate text (alt text)", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD045" | ||
}, | ||
@@ -1441,21 +873,3 @@ "MD046": { | ||
"code-block-style": { | ||
"description": "MD046/code-block-style - Code block style", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Block style", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"fenced", | ||
"indented" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD046" | ||
}, | ||
@@ -1468,5 +882,3 @@ "MD047": { | ||
"single-trailing-newline": { | ||
"description": "MD047/single-trailing-newline - Files should end with a single newline character", | ||
"type": "boolean", | ||
"default": true | ||
"$ref": "#/properties/MD047" | ||
}, | ||
@@ -1495,21 +907,3 @@ "MD048": { | ||
"code-fence-style": { | ||
"description": "MD048/code-fence-style - Code fence style", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Code fence style", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"backtick", | ||
"tilde" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD048" | ||
}, | ||
@@ -1538,21 +932,3 @@ "MD049": { | ||
"emphasis-style": { | ||
"description": "MD049/emphasis-style - Emphasis style should be consistent", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Emphasis style should be consistent", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"asterisk", | ||
"underscore" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD049" | ||
}, | ||
@@ -1581,22 +957,28 @@ "MD050": { | ||
"strong-style": { | ||
"description": "MD050/strong-style - Strong style should be consistent", | ||
"type": [ | ||
"boolean", | ||
"object" | ||
], | ||
"default": true, | ||
"properties": { | ||
"style": { | ||
"description": "Strong style should be consistent", | ||
"type": "string", | ||
"enum": [ | ||
"consistent", | ||
"asterisk", | ||
"underscore" | ||
], | ||
"default": "consistent" | ||
} | ||
}, | ||
"additionalProperties": false | ||
"$ref": "#/properties/MD050" | ||
}, | ||
"MD051": { | ||
"description": "MD051/link-fragments - Link fragments should be valid", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"link-fragments": { | ||
"$ref": "#/properties/MD051" | ||
}, | ||
"MD052": { | ||
"description": "MD052/reference-links-images - Reference links and images should use a label that is defined", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"reference-links-images": { | ||
"$ref": "#/properties/MD052" | ||
}, | ||
"MD053": { | ||
"description": "MD053/link-image-reference-definitions - Link and image reference definitions should be needed", | ||
"type": "boolean", | ||
"default": true | ||
}, | ||
"link-image-reference-definitions": { | ||
"$ref": "#/properties/MD053" | ||
}, | ||
"headings": { | ||
@@ -1638,3 +1020,3 @@ "description": "headings - MD001, MD002, MD003, MD018, MD019, MD020, MD021, MD022, MD023, MD024, MD025, MD026, MD036, MD041, MD043", | ||
"links": { | ||
"description": "links - MD011, MD034, MD039, MD042", | ||
"description": "links - MD011, MD034, MD039, MD042, MD051, MD052, MD053", | ||
"type": "boolean", | ||
@@ -1719,3 +1101,3 @@ "default": true | ||
"images": { | ||
"description": "images - MD045", | ||
"description": "images - MD045, MD052, MD053", | ||
"type": "boolean", | ||
@@ -1722,0 +1104,0 @@ "default": true |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
513959
71
10904
1027
23
+ Addedentities@3.0.1(transitive)
+ Addedlinkify-it@4.0.1(transitive)
+ Addedmarkdown-it@13.0.1(transitive)
- Removedentities@2.1.0(transitive)
- Removedlinkify-it@3.0.3(transitive)
- Removedmarkdown-it@12.3.2(transitive)
Updatedmarkdown-it@13.0.1