Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

markdownlint

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

markdownlint - npm Package Compare versions

Comparing version 0.25.1 to 0.26.0

lib/md049-md050.js

2

CONTRIBUTING.md

@@ -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`.

@@ -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"
]
}
} -->

619

helpers/helpers.js

@@ -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": [

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc