Socket
Socket
Sign inDemoInstall

markdownlint

Package Overview
Dependencies
Maintainers
1
Versions
68
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.29.0 to 0.30.0

helpers/shared.js

7

CHANGELOG.md
# Changelog
## 0.30.0
- Use `micromark` in MD022/MD026/MD032/MD037/MD045/MD051
- Incorporate `micromark-extension-math` for math syntax
- Allow custom rules to override information URL
- Update dependencies
## 0.29.0

@@ -4,0 +11,0 @@

2

doc/CustomRules.md

@@ -85,2 +85,4 @@ # Custom Rules

location.
- `information` is an optional (absolute) `URL` of a link to override the
same-named value provided by the rule definition. (Uncommon)
- `range` is an optional `Array` with two `Number` values identifying the

@@ -87,0 +89,0 @@ 1-based column and length of the error.

16

doc/md022.md

@@ -9,4 +9,4 @@ # `MD022` - Headings should be surrounded by blank lines

- `lines_above`: Blank lines above heading (`integer`, default `1`)
- `lines_below`: Blank lines below heading (`integer`, default `1`)
- `lines_above`: Blank lines above heading (`integer|integer[]`, default `1`)
- `lines_below`: Blank lines below heading (`integer|integer[]`, default `1`)

@@ -40,6 +40,12 @@ Fixable: Some violations can be fixed by tooling

The `lines_above` and `lines_below` parameters can be used to specify a
different number of blank lines (including 0) above or below each heading.
different number of blank lines (including `0`) above or below each heading.
If the value `-1` is used for either parameter, any number of blank lines is
allowed. To customize the number of lines above or below each heading level
individually, specify a `number[]` where values correspond to heading levels
1-6 (in order).
Note: If `lines_above` or `lines_below` are configured to require more than one
blank line, [MD012/no-multiple-blanks](md012.md) should also be customized.
Notes: If `lines_above` or `lines_below` are configured to require more than one
blank line, [MD012/no-multiple-blanks](md012.md) should also be customized. This
rule checks for *at least* as many blank lines as specified; any extra blank
lines are ignored.

@@ -46,0 +52,0 @@ Rationale: Aside from aesthetic reasons, some parsers, including `kramdown`,

@@ -9,4 +9,3 @@ # `MD026` - Trailing punctuation in heading

- `punctuation`: Punctuation characters not allowed at end of headings
(`string`, default `.,;:!。,;:!`)
- `punctuation`: Punctuation characters (`string`, default `.,;:!。,;:!`)

@@ -13,0 +12,0 @@ Fixable: Some violations can be fixed by tooling

@@ -9,4 +9,4 @@ # `MD049` - Emphasis style should be consistent

- `style`: Emphasis style should be consistent (`string`, default `consistent`,
values `asterisk` / `consistent` / `underscore`)
- `style`: Emphasis style (`string`, default `consistent`, values `asterisk` /
`consistent` / `underscore`)

@@ -13,0 +13,0 @@ Fixable: Some violations can be fixed by tooling

@@ -9,4 +9,4 @@ # `MD050` - Strong style should be consistent

- `style`: Strong style should be consistent (`string`, default `consistent`,
values `asterisk` / `consistent` / `underscore`)
- `style`: Strong style (`string`, default `consistent`, values `asterisk` /
`consistent` / `underscore`)

@@ -13,0 +13,0 @@ Fixable: Some violations can be fixed by tooling

@@ -13,3 +13,3 @@ # `MD051` - Link fragments should be valid

```markdown
# Title
# Heading Name

@@ -19,14 +19,25 @@ [Link](#fragment)

To fix this issue, change the link fragment to reference an existing heading:
To fix this issue, change the link fragment to reference an existing heading's
generated name (see below):
```markdown
# Title
# Heading Name
[Link](#title)
[Link](#heading-name)
```
Alternatively, an HTML `a` tag with an `id` or a `name` attribute can be used to
define a fragment:
Alternatively, some platforms allow the syntax `{#named-anchor}` to be used
within a heading to provide a specific name (consisting of only lower-case
letters, numbers, `-`, and `_`):
```markdown
# Heading Name {#custom-name}
[Link](#custom-name)
```
Alternatively, any HTML tag with an `id` attribute or an `a` tag with a `name`
attribute can be used to define a fragment:
```markdown
<a id="bookmark"></a>

@@ -33,0 +44,0 @@

@@ -809,4 +809,4 @@ # Rules

- `lines_above`: Blank lines above heading (`integer`, default `1`)
- `lines_below`: Blank lines below heading (`integer`, default `1`)
- `lines_above`: Blank lines above heading (`integer|integer[]`, default `1`)
- `lines_below`: Blank lines below heading (`integer|integer[]`, default `1`)

@@ -840,6 +840,12 @@ Fixable: Some violations can be fixed by tooling

The `lines_above` and `lines_below` parameters can be used to specify a
different number of blank lines (including 0) above or below each heading.
different number of blank lines (including `0`) above or below each heading.
If the value `-1` is used for either parameter, any number of blank lines is
allowed. To customize the number of lines above or below each heading level
individually, specify a `number[]` where values correspond to heading levels
1-6 (in order).
Note: If `lines_above` or `lines_below` are configured to require more than one
blank line, [MD012/no-multiple-blanks](md012.md) should also be customized.
Notes: If `lines_above` or `lines_below` are configured to require more than one
blank line, [MD012/no-multiple-blanks](md012.md) should also be customized. This
rule checks for *at least* as many blank lines as specified; any extra blank
lines are ignored.

@@ -991,4 +997,3 @@ Rationale: Aside from aesthetic reasons, some parsers, including `kramdown`,

- `punctuation`: Punctuation characters not allowed at end of headings
(`string`, default `.,;:!。,;:!`)
- `punctuation`: Punctuation characters (`string`, default `.,;:!。,;:!`)

@@ -2085,4 +2090,4 @@ Fixable: Some violations can be fixed by tooling

- `style`: Emphasis style should be consistent (`string`, default `consistent`,
values `asterisk` / `consistent` / `underscore`)
- `style`: Emphasis style (`string`, default `consistent`, values `asterisk` /
`consistent` / `underscore`)

@@ -2121,4 +2126,4 @@ Fixable: Some violations can be fixed by tooling

- `style`: Strong style should be consistent (`string`, default `consistent`,
values `asterisk` / `consistent` / `underscore`)
- `style`: Strong style (`string`, default `consistent`, values `asterisk` /
`consistent` / `underscore`)

@@ -2161,3 +2166,3 @@ Fixable: Some violations can be fixed by tooling

```markdown
# Title
# Heading Name

@@ -2167,14 +2172,25 @@ [Link](#fragment)

To fix this issue, change the link fragment to reference an existing heading:
To fix this issue, change the link fragment to reference an existing heading's
generated name (see below):
```markdown
# Title
# Heading Name
[Link](#title)
[Link](#heading-name)
```
Alternatively, an HTML `a` tag with an `id` or a `name` attribute can be used to
define a fragment:
Alternatively, some platforms allow the syntax `{#named-anchor}` to be used
within a heading to provide a specific name (consisting of only lower-case
letters, numbers, `-`, and `_`):
```markdown
# Heading Name {#custom-name}
[Link](#custom-name)
```
Alternatively, any HTML tag with an `id` attribute or an `a` tag with a `name`
attribute can be used to define a fragment:
```markdown
<a id="bookmark"></a>

@@ -2181,0 +2197,0 @@

@@ -7,5 +7,3 @@ // @ts-check

// Regular expression for matching common newline characters
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
const newLineRe = /\r\n?|\n/g;
const { newLineRe } = require("./shared.js");
module.exports.newLineRe = newLineRe;

@@ -24,6 +22,2 @@

// Regular expression for matching HTML elements
const htmlElementRe = /<(([A-Za-z][A-Za-z\d-]*)(?:\s[^`>]*)?)\/?>/g;
module.exports.htmlElementRe = htmlElementRe;
// Regular expressions for range matching

@@ -33,5 +27,2 @@ module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;

// Regular expression for all instances of emphasis markers
const emphasisMarkersRe = /[_*]/g;
// Regular expression for blockquote prefixes

@@ -45,2 +36,12 @@ const blockquotePrefixRe = /^[>\s]*/;

// Regular expression for identifying an HTML entity at the end of a line
module.exports.endOfLineHtmlEntityRe =
// eslint-disable-next-line max-len
/&(?:#\d+|#[xX][\da-fA-F]+|[a-zA-Z]{2,31}|blk\d{2}|emsp1[34]|frac\d{2}|sup\d|there4);$/;
// Regular expression for identifying a GitHub emoji code at the end of a line
module.exports.endOfLineGemojiCodeRe =
// eslint-disable-next-line max-len
/:(?:[abmovx]|[-+]1|100|1234|(?:1st|2nd|3rd)_place_medal|8ball|clock\d{1,4}|e-mail|non-potable_water|o2|t-rex|u5272|u5408|u55b6|u6307|u6708|u6709|u6e80|u7121|u7533|u7981|u7a7a|[a-z]{2,15}2?|[a-z]{1,14}(?:_[a-z\d]{1,16})+):$/;
// All punctuation characters (normal and full-width)

@@ -53,23 +54,80 @@ const allPunctuation = ".,;:!?。,;:!?";

// Returns true iff the input is a number
module.exports.isNumber = function isNumber(obj) {
/**
* Returns true iff the input is a Number.
*
* @param {Object} obj Object of unknown type.
* @returns {boolean} True iff obj is a Number.
*/
function isNumber(obj) {
return typeof obj === "number";
};
}
module.exports.isNumber = isNumber;
// Returns true iff the input is a string
module.exports.isString = function isString(obj) {
/**
* Returns true iff the input is a String.
*
* @param {Object} obj Object of unknown type.
* @returns {boolean} True iff obj is a String.
*/
function isString(obj) {
return typeof obj === "string";
};
}
module.exports.isString = isString;
// Returns true iff the input string is empty
module.exports.isEmptyString = function isEmptyString(str) {
/**
* Returns true iff the input String is empty.
*
* @param {string} str String of unknown length.
* @returns {boolean} True iff the input String is empty.
*/
function isEmptyString(str) {
return str.length === 0;
};
}
module.exports.isEmptyString = isEmptyString;
// Returns true iff the input is an object
module.exports.isObject = function isObject(obj) {
return (obj !== null) && (typeof obj === "object") && !Array.isArray(obj);
};
/**
* Returns true iff the input is an Object.
*
* @param {Object} obj Object of unknown type.
* @returns {boolean} True iff obj is an Object.
*/
function isObject(obj) {
return !!obj && (typeof obj === "object") && !Array.isArray(obj);
}
module.exports.isObject = isObject;
/**
* Returns true iff the input is a URL.
*
* @param {Object} obj Object of unknown type.
* @returns {boolean} True iff obj is a URL.
*/
function isUrl(obj) {
return !!obj && (Object.getPrototypeOf(obj) === URL.prototype);
}
module.exports.isUrl = isUrl;
/**
* Clones the input if it is an Array.
*
* @param {Object} arr Object of unknown type.
* @returns {Object} Clone of obj iff obj is an Array.
*/
function cloneIfArray(arr) {
return Array.isArray(arr) ? [ ...arr ] : arr;
}
module.exports.cloneIfArray = cloneIfArray;
/**
* Clones the input if it is a URL.
*
* @param {Object} url Object of unknown type.
* @returns {Object} Clone of obj iff obj is a URL.
*/
function cloneIfUrl(url) {
return isUrl(url) ? new URL(url) : url;
}
module.exports.cloneIfUrl = cloneIfUrl;
/**
* Returns true iff the input line is blank (contains nothing, whitespace, or

@@ -289,21 +347,6 @@ * comments (unclosed start/end comments allowed)).

/**
* Returns whether a token is a math block (created by markdown-it-texmath).
*
* @param {Object} token MarkdownItToken instance.
* @returns {boolean} True iff token is a math block.
*/
function isMathBlock(token) {
return (
((token.tag === "$$") || (token.tag === "math")) &&
token.type.startsWith("math_block") &&
!token.type.endsWith("_end")
);
}
module.exports.isMathBlock = isMathBlock;
// Get line metadata array
module.exports.getLineMetadata = function getLineMetadata(params) {
const lineMetadata = params.lines.map(
(line, index) => [ line, index, false, 0, false, false, false, false ]
(line, index) => [ line, index, false, 0, false, false, false ]
);

@@ -337,7 +380,2 @@ filterTokens(params, "fence", (token) => {

});
for (const token of params.parsers.markdownit.tokens.filter(isMathBlock)) {
for (let i = token.map[0]; i < token.map[1]; i++) {
lineMetadata[i][7] = true;
}
}
return lineMetadata;

@@ -351,3 +389,3 @@ };

* @param {Function} handler Function taking (line, lineIndex, inCode, onFence,
* inTable, inItem, inBreak, inMath).
* inTable, inItem, inBreak).
* @returns {void}

@@ -413,19 +451,2 @@ */

/**
* Calls the provided function for each specified inline child token.
*
* @param {Object} params RuleParams instance.
* @param {string} type Token type identifier.
* @param {Function} handler Callback function.
* @returns {void}
*/
function forEachInlineChild(params, type, handler) {
filterTokens(params, "inline", (token) => {
for (const child of token.children.filter((c) => c.type === type)) {
handler(child, token);
}
});
}
module.exports.forEachInlineChild = forEachInlineChild;
// Calls the provided function for each heading's content

@@ -601,47 +622,2 @@ module.exports.forEachHeading = function forEachHeading(params, handler) {

/**
* 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 = [];
// Match with htmlElementRe
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 ]);
}
});
// Match with html_inline
forEachInlineChild(params, "html_inline", (token, parent) => {
const parentContent = parent.content;
let tokenContent = token.content;
const parentIndex = parentContent.indexOf(tokenContent);
let deltaLines = 0;
let indent = 0;
for (let i = parentIndex - 1; i >= 0; i--) {
if (parentContent[i] === "\n") {
deltaLines++;
} else if (deltaLines === 0) {
indent++;
}
}
let lineIndex = token.lineNumber - 1 + deltaLines;
do {
const index = tokenContent.indexOf("\n");
const length = (index === -1) ? tokenContent.length : index;
exclusions.push([ lineIndex, indent, length ]);
tokenContent = tokenContent.slice(length + 1);
lineIndex++;
indent = 0;
} while (tokenContent.length > 0);
});
// Return results
return exclusions;
};
/**
* Determines whether the specified range is within another range.

@@ -691,125 +667,2 @@ *

/**
* 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.
*
* @param {Object} params RuleParams instance.
* @returns {number[][]} List of markers.
*/
function emphasisMarkersInContent(params) {
const { lines } = params;
const byLine = new Array(lines.length);
// Search links
for (const [ tokenLineIndex, tokenLine ] of lines.entries()) {
const inLine = [];
forEachLink(tokenLine, (index, match) => {
let markerMatch = null;
while ((markerMatch = emphasisMarkersRe.exec(match))) {
inLine.push(index + markerMatch.index);
}
});
byLine[tokenLineIndex] = inLine;
}
// Search code spans
filterTokens(params, "inline", (token) => {
const { children, lineNumber, map } = token;
if (children.some((child) => child.type === "code_inline")) {
const tokenLines = lines.slice(map[0], map[1]);
forEachInlineCodeSpan(
tokenLines.join("\n"),
(code, lineIndex, column, tickCount) => {
const codeLines = code.split(newLineRe);
for (const [ codeLineIndex, codeLine ] of codeLines.entries()) {
const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex;
const inLine = byLine[byLineIndex];
const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount;
let match = null;
while ((match = emphasisMarkersRe.exec(codeLine))) {
inLine.push(codeLineOffset + match.index);
}
byLine[byLineIndex] = inLine;
}
}
);
}
});
return byLine;
}
module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
/**
* Returns an object with information about reference links and images.

@@ -816,0 +669,0 @@ *

{
"name": "markdownlint-rule-helpers",
"version": "0.20.0",
"version": "0.21.0",
"description": "A collection of markdownlint helper functions for custom rules",

@@ -16,3 +16,3 @@ "main": "./helpers.js",

"engines": {
"node": ">=14.18.0"
"node": ">=16"
},

@@ -19,0 +19,0 @@ "dependencies": {

@@ -18,4 +18,2 @@ // @ts-check

() => map.get("flattenedLists");
module.exports.htmlElementRanges =
() => map.get("htmlElementRanges");
module.exports.lineMetadata =

@@ -22,0 +20,0 @@ () => map.get("lineMetadata");

@@ -14,2 +14,2 @@ // @ts-check

module.exports.homepage = "https://github.com/DavidAnson/markdownlint";
module.exports.version = "0.29.0";
module.exports.version = "0.30.0";

@@ -222,2 +222,6 @@ export = markdownlint;

/**
* Link to more information.
*/
information?: URL;
/**
* Column number (1-based) and length.

@@ -224,0 +228,0 @@ */

@@ -62,3 +62,3 @@ // @ts-check

rule.information &&
(Object.getPrototypeOf(rule.information) !== URL.prototype)
!helpers.isUrl(rule.information)
) {

@@ -485,3 +485,3 @@ result = newError("information");

parameter,
enabledRulesPerLineNumber[nextLineNumber] || {}
enabledRulesPerLineNumber[nextLineNumber]
);

@@ -588,4 +588,2 @@ }

helpers.flattenLists(paramsBase.parsers.markdownit.tokens);
const htmlElementRanges =
helpers.htmlElementRanges(paramsBase, lineMetadata);
const referenceLinkImageData =

@@ -596,3 +594,2 @@ helpers.getReferenceLinkImageData(paramsBase);

flattenedLists,
htmlElementRanges,
lineMetadata,

@@ -636,2 +633,6 @@ referenceLinkImageData

}
if (errorInfo.information &&
!helpers.isUrl(errorInfo.information)) {
throwError("information");
}
if (errorInfo.range &&

@@ -689,2 +690,3 @@ (!Array.isArray(errorInfo.range) ||

}
const information = errorInfo.information || rule.information;
results.push({

@@ -695,3 +697,3 @@ lineNumber,

"ruleDescription": rule.description,
"ruleInformation": rule.information ? rule.information.href : null,
"ruleInformation": information ? information.href : null,
"errorDetail": errorInfo.detail || null,

@@ -874,4 +876,15 @@ "errorContext": errorInfo.context || null,

callback = callback || function noop() {};
const customRuleList =
[ options.customRules || [] ]
.flat()
.map((rule) => ({
"names": helpers.cloneIfArray(rule.names),
"description": rule.description,
"information": helpers.cloneIfUrl(rule.information),
"tags": helpers.cloneIfArray(rule.tags),
"asynchronous": rule.asynchronous,
"function": rule.function
}));
// eslint-disable-next-line unicorn/prefer-spread
const ruleList = rules.concat(options.customRules || []);
const ruleList = rules.concat(customRuleList);
const ruleErr = validateRuleList(ruleList, synchronous);

@@ -1325,2 +1338,3 @@ if (ruleErr) {

* @property {string} [context] Context for the error.
* @property {URL} [information] Link to more information.
* @property {number[]} [range] Column number (1-based) and length.

@@ -1327,0 +1341,0 @@ * @property {RuleOnErrorFixInfo} [fixInfo] Fix information.

@@ -5,5 +5,22 @@ // @ts-check

const { addErrorDetailIf, blockquotePrefixRe, filterTokens, isBlankLine } =
const { addErrorDetailIf, blockquotePrefixRe, isBlankLine } =
require("../helpers");
const { filterByTypes, getHeadingLevel } =
require("../helpers/micromark.cjs");
const defaultLines = 1;
const getLinesFunction = (linesParam) => {
if (Array.isArray(linesParam)) {
const linesArray = new Array(6).fill(defaultLines);
for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) {
linesArray[index] = value;
}
return (heading) => linesArray[getHeadingLevel(heading) - 1];
}
// Coerce linesParam to a number
const lines = (linesParam === undefined) ? defaultLines : Number(linesParam);
return () => lines;
};
const getBlockQuote = (str, count) => (

@@ -23,48 +40,71 @@ (str || "")

"function": function MD022(params, onError) {
let linesAbove = params.config.lines_above;
linesAbove = Number((linesAbove === undefined) ? 1 : linesAbove);
let linesBelow = params.config.lines_below;
linesBelow = Number((linesBelow === undefined) ? 1 : linesBelow);
const { lines } = params;
filterTokens(params, "heading_open", (token) => {
const [ topIndex, nextIndex ] = token.map;
let actualAbove = 0;
for (let i = 0; i < linesAbove; i++) {
if (isBlankLine(lines[topIndex - i - 1])) {
const getLinesAbove = getLinesFunction(params.config.lines_above);
const getLinesBelow = getLinesFunction(params.config.lines_below);
const { lines, parsers } = params;
const headings = filterByTypes(
parsers.micromark.tokens,
[ "atxHeading", "setextHeading" ]
);
for (const heading of headings) {
const { startLine, endLine } = heading;
const line = lines[startLine - 1].trim();
// Check lines above
const linesAbove = getLinesAbove(heading);
if (linesAbove >= 0) {
let actualAbove = 0;
for (
let i = 0;
(i < linesAbove) && isBlankLine(lines[startLine - 2 - i]);
i++
) {
actualAbove++;
}
addErrorDetailIf(
onError,
startLine,
linesAbove,
actualAbove,
"Above",
line,
null,
{
"insertText": getBlockQuote(
lines[startLine - 2],
linesAbove - actualAbove
)
}
);
}
addErrorDetailIf(
onError,
topIndex + 1,
linesAbove,
actualAbove,
"Above",
lines[topIndex].trim(),
null,
{
"insertText":
getBlockQuote(lines[topIndex - 1], linesAbove - actualAbove)
});
let actualBelow = 0;
for (let i = 0; i < linesBelow; i++) {
if (isBlankLine(lines[nextIndex + i])) {
// Check lines below
const linesBelow = getLinesBelow(heading);
if (linesBelow >= 0) {
let actualBelow = 0;
for (
let i = 0;
(i < linesBelow) && isBlankLine(lines[endLine + i]);
i++
) {
actualBelow++;
}
addErrorDetailIf(
onError,
startLine,
linesBelow,
actualBelow,
"Below",
line,
null,
{
"lineNumber": endLine + 1,
"insertText": getBlockQuote(
lines[endLine],
linesBelow - actualBelow
)
}
);
}
addErrorDetailIf(
onError,
topIndex + 1,
linesBelow,
actualBelow,
"Below",
lines[topIndex].trim(),
null,
{
"lineNumber": nextIndex + 1,
"insertText":
getBlockQuote(lines[nextIndex], linesBelow - actualBelow)
});
});
}
}
};

@@ -5,6 +5,6 @@ // @ts-check

const { addError, allPunctuationNoQuestion, escapeForRegExp, forEachHeading } =
require("../helpers");
const { addError, allPunctuationNoQuestion, endOfLineGemojiCodeRe,
endOfLineHtmlEntityRe, escapeForRegExp } = require("../helpers");
const { filterByTypes } = require("../helpers/micromark.cjs");
const endOfLineHtmlEntityRe = /&#?[\da-zA-Z]+;$/;

@@ -22,15 +22,22 @@ module.exports = {

new RegExp("\\s*[" + escapeForRegExp(punctuation) + "]+$");
forEachHeading(params, (heading) => {
const { line, lineNumber } = heading;
const trimmedLine = line.replace(/([^\s#])[\s#]+$/, "$1");
const match = trailingPunctuationRe.exec(trimmedLine);
if (match && !endOfLineHtmlEntityRe.test(trimmedLine)) {
const headings = filterByTypes(
params.parsers.micromark.tokens,
[ "atxHeadingText", "setextHeadingText" ]
);
for (const heading of headings) {
const { endLine, startColumn, text } = heading;
const match = trailingPunctuationRe.exec(text);
if (
match &&
!endOfLineHtmlEntityRe.test(text) &&
!endOfLineGemojiCodeRe.test(text)
) {
const fullMatch = match[0];
const column = match.index + 1;
const column = startColumn + match.index;
const length = fullMatch.length;
addError(
onError,
lineNumber,
endLine,
`Punctuation: '${fullMatch}'`,
null,
undefined,
[ column, length ],

@@ -43,4 +50,4 @@ {

}
});
}
}
};

@@ -7,4 +7,34 @@ // @ts-check

require("../helpers");
const { flattenedLists } = require("./cache");
const { filterByPredicate, flattenedChildren } =
require("../helpers/micromark.cjs");
const nonContentTokens = new Set([
"blockQuoteMarker",
"blockQuotePrefix",
"blockQuotePrefixWhitespace",
"lineEnding",
"lineEndingBlank",
"linePrefix",
"listItemIndent"
]);
const isList = (token) => (
(token.type === "listOrdered") || (token.type === "listUnordered")
);
const addBlankLineError = (onError, lines, lineIndex, lineNumber) => {
const line = lines[lineIndex];
const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd();
addErrorContext(
onError,
lineIndex + 1,
line.trim(),
null,
null,
null,
{
lineNumber,
"insertText": `${quotePrefix}\n`
}
);
};
module.exports = {

@@ -15,35 +45,31 @@ "names": [ "MD032", "blanks-around-lists" ],

"function": function MD032(params, onError) {
const { lines } = params;
const filteredLists = flattenedLists().filter((list) => !list.nesting);
for (const list of filteredLists) {
const firstIndex = list.open.map[0];
const { lines, parsers } = params;
// For every top-level list...
const topLevelLists = filterByPredicate(
parsers.micromark.tokens,
isList,
(token) => (isList(token) ? [] : token.children)
);
for (const list of topLevelLists) {
// Look for a blank line above the list
const firstIndex = list.startLine - 1;
if (!isBlankLine(lines[firstIndex - 1])) {
const line = lines[firstIndex];
const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd();
addErrorContext(
onError,
firstIndex + 1,
line.trim(),
null,
null,
null,
{
"insertText": `${quotePrefix}\n`
});
addBlankLineError(onError, lines, firstIndex);
}
const lastIndex = list.lastLineIndex - 1;
// Find the "visual" end of the list
let endLine = list.endLine;
for (const child of flattenedChildren(list).reverse()) {
if (!nonContentTokens.has(child.type)) {
endLine = child.endLine;
break;
}
}
// Look for a blank line below the list
const lastIndex = endLine - 1;
if (!isBlankLine(lines[lastIndex + 1])) {
const line = lines[lastIndex];
const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd();
addErrorContext(
onError,
lastIndex + 1,
line.trim(),
null,
null,
null,
{
"lineNumber": lastIndex + 2,
"insertText": `${quotePrefix}\n`
});
addBlankLineError(onError, lines, lastIndex, lastIndex + 2);
}

@@ -50,0 +76,0 @@ }

@@ -6,3 +6,3 @@ // @ts-check

const { addError } = require("../helpers");
const { filterByTypes, getHtmlTagInfo, parse } =
const { filterByHtmlTokens, getHtmlTagInfo } =
require("../helpers/micromark.cjs");

@@ -20,46 +20,20 @@

allowedElements = allowedElements.map((element) => element.toLowerCase());
const pending = [ [ 0, params.parsers.micromark.tokens ] ];
let current = null;
while ((current = pending.shift())) {
const [ offset, tokens ] = current;
for (const token of filterByTypes(tokens, [ "htmlFlow", "htmlText" ])) {
if (token.type === "htmlText") {
const htmlTagInfo = getHtmlTagInfo(token);
if (
htmlTagInfo &&
!htmlTagInfo.close &&
!allowedElements.includes(htmlTagInfo.name.toLowerCase())
) {
const range = [
token.startColumn,
token.text.replace(nextLinesRe, "").length
];
addError(
onError,
token.startLine + offset,
"Element: " + htmlTagInfo.name,
undefined,
range
);
}
} else {
// token.type === "htmlFlow"
// Re-parse without "htmlFlow" to get only "htmlText" tokens
const options = {
"extensions": [
{
"disable": {
"null": [ "codeIndented", "htmlFlow" ]
}
}
]
};
// Use lines instead of token.text for accurate columns
const lines =
params.lines.slice(token.startLine - 1, token.endLine).join("\n");
const flowTokens = parse(lines, options);
pending.push(
[ token.startLine - 1, flowTokens ]
);
}
for (const token of filterByHtmlTokens(params.parsers.micromark.tokens)) {
const htmlTagInfo = getHtmlTagInfo(token);
if (
htmlTagInfo &&
!htmlTagInfo.close &&
!allowedElements.includes(htmlTagInfo.name.toLowerCase())
) {
const range = [
token.startColumn,
token.text.replace(nextLinesRe, "").length
];
addError(
onError,
token.startLine,
"Element: " + htmlTagInfo.name,
undefined,
range
);
}

@@ -66,0 +40,0 @@ }

@@ -5,13 +5,6 @@ // @ts-check

const { addErrorContext, emphasisMarkersInContent, forEachLine, isBlankLine,
withinAnyRange } = require("../helpers");
const { htmlElementRanges, lineMetadata } = require("./cache");
const { addError } = require("../helpers");
const emphasisRe = /(^|[^\\]|\\\\)(?:(\*{1,3})|(_{1,3}))/g;
const embeddedUnderscoreRe = /([A-Za-z\d])(_([A-Za-z\d]))+/g;
const asteriskListItemMarkerRe = /^([\s>]*)\*(\s+)/;
const leftSpaceRe = /^\s+/;
const rightSpaceRe = /\s+$/;
const tablePipeRe = /\|/;
const allUnderscoresRe = /_/g;
const emphasisStartTextRe = /^(\S{1,3})(\s+)\S/;
const emphasisEndTextRe = /\S(\s+)(\S{1,3})$/;

@@ -23,165 +16,85 @@ module.exports = {

"function": function MD037(params, onError) {
const exclusions = htmlElementRanges();
// eslint-disable-next-line init-declarations
let effectiveEmphasisLength, emphasisIndex, emphasisKind, emphasisLength,
pendingError = null;
// eslint-disable-next-line jsdoc/require-jsdoc
function resetRunTracking() {
emphasisIndex = -1;
emphasisLength = 0;
emphasisKind = "";
effectiveEmphasisLength = 0;
pendingError = null;
// Initialize variables
const { lines, parsers } = params;
const emphasisTokensByMarker = new Map();
for (const marker of [ "_", "__", "___", "*", "**", "***" ]) {
emphasisTokensByMarker.set(marker, []);
}
// eslint-disable-next-line jsdoc/require-jsdoc
function handleRunEnd(
line, lineIndex, contextLength, match, matchIndex, inTable
) {
// Close current run
let content = line.substring(emphasisIndex, matchIndex);
if (!emphasisLength) {
content = content.trimStart();
const pending = [ ...parsers.micromark.tokens ];
let token = null;
while ((token = pending.shift())) {
// Use reparsed children of htmlFlow tokens
if (token.type === "htmlFlow") {
pending.unshift(...token.htmlFlowChildren);
continue;
}
if (!match) {
content = content.trimEnd();
pending.push(...token.children);
// Build lists of bare tokens for each emphasis marker type
for (const emphasisTokens of emphasisTokensByMarker.values()) {
emphasisTokens.length = 0;
}
const leftSpace = leftSpaceRe.test(content);
const rightSpace = rightSpaceRe.test(content);
if (
(leftSpace || rightSpace) &&
(!inTable || !tablePipeRe.test(content))
) {
// Report the violation
const contextStart = emphasisIndex - emphasisLength;
const contextEnd = matchIndex + contextLength;
const column = contextStart + 1;
const length = contextEnd - contextStart;
if (!withinAnyRange(exclusions, lineIndex, column, length)) {
const context = line.substring(contextStart, contextEnd);
const leftMarker = line.substring(contextStart, emphasisIndex);
const rightMarker = match ? (match[2] || match[3]) : "";
const fixedText = `${leftMarker}${content.trim()}${rightMarker}`;
return [
onError,
lineIndex + 1,
context,
leftSpace,
rightSpace,
[ column, length ],
{
"editColumn": column,
"deleteCount": length,
"insertText": fixedText
}
];
for (const child of token.children) {
const { text, type } = child;
if ((type === "data") && (text.length <= 3)) {
const emphasisTokens = emphasisTokensByMarker.get(text);
if (emphasisTokens) {
emphasisTokens.push(child);
}
}
}
return null;
}
// Initialize
const ignoreMarkersByLine = emphasisMarkersInContent(params);
resetRunTracking();
forEachLine(
lineMetadata(),
(line, lineIndex, inCode, onFence, inTable, inItem, onBreak, inMath) => {
const onItemStart = (inItem === 1);
if (
inCode ||
onFence ||
inTable ||
onBreak ||
onItemStart ||
isBlankLine(line)
) {
// Emphasis resets when leaving a block
resetRunTracking();
}
if (
inCode ||
onFence ||
onBreak ||
inMath
) {
// Emphasis has no meaning here
return;
}
let patchedLine = line.replace(
embeddedUnderscoreRe,
(match) => match.replace(allUnderscoresRe, " ")
);
if (onItemStart) {
// Trim overlapping '*' list item marker
patchedLine = patchedLine.replace(asteriskListItemMarkerRe, "$1 $2");
}
let match = null;
// Match all emphasis-looking runs in the line...
while ((match = emphasisRe.exec(patchedLine))) {
const ignoreMarkersForLine = ignoreMarkersByLine[lineIndex];
const matchIndex = match.index + match[1].length;
if (ignoreMarkersForLine.includes(matchIndex)) {
// Ignore emphasis markers inside code spans and links
continue;
// Process bare tokens for each emphasis marker type
for (const emphasisTokens of emphasisTokensByMarker.values()) {
for (let i = 0; i + 1 < emphasisTokens.length; i += 2) {
// Process start token of start/end pair
const startToken = emphasisTokens[i];
const startText =
lines[startToken.startLine - 1].slice(startToken.startColumn - 1);
const startMatch = startText.match(emphasisStartTextRe);
if (startMatch) {
const [ startContext, startMarker, startSpaces ] = startMatch;
if ((startMarker === startToken.text) && (startSpaces.length > 0)) {
addError(
onError,
startToken.startLine,
undefined,
startContext,
[ startToken.startColumn, startContext.length ],
{
"editColumn": startToken.endColumn,
"deleteCount": startSpaces.length
}
);
}
}
const matchLength = match[0].length - match[1].length;
const matchKind = (match[2] || match[3])[0];
if (emphasisIndex === -1) {
// New run
emphasisIndex = matchIndex + matchLength;
emphasisLength = matchLength;
emphasisKind = matchKind;
effectiveEmphasisLength = matchLength;
} else if (matchKind === emphasisKind) {
// Matching emphasis markers
if (matchLength === effectiveEmphasisLength) {
// Ending an existing run, report any pending error
if (pendingError) {
// @ts-ignore
addErrorContext(...pendingError);
pendingError = null;
}
const error = handleRunEnd(
line,
lineIndex,
effectiveEmphasisLength,
match,
matchIndex,
inTable
// Process end token of start/end pair
const endToken = emphasisTokens[i + 1];
const endText =
lines[endToken.startLine - 1].slice(0, endToken.endColumn - 1);
const endMatch = endText.match(emphasisEndTextRe);
if (endMatch) {
const [ endContext, endSpace, endMarker ] = endMatch;
if ((endMarker === endToken.text) && (endSpace.length > 0)) {
addError(
onError,
endToken.startLine,
undefined,
endContext,
[ endToken.endColumn - endContext.length, endContext.length ],
{
"editColumn": endToken.startColumn - endSpace.length,
"deleteCount": endSpace.length
}
);
if (error) {
// @ts-ignore
addErrorContext(...error);
}
// Reset
resetRunTracking();
} else if (matchLength === 3) {
// Swap internal run length (1->2 or 2->1)
effectiveEmphasisLength = matchLength - effectiveEmphasisLength;
} else if (effectiveEmphasisLength === 3) {
// Downgrade internal run (3->1 or 3->2)
effectiveEmphasisLength -= matchLength;
} else {
// Upgrade to internal run (1->3 or 2->3)
effectiveEmphasisLength += matchLength;
}
// Back up one character so RegExp has a chance to match the
// next marker (ex: "**star**_underscore_")
if (emphasisRe.lastIndex > 1) {
emphasisRe.lastIndex--;
}
} else if (emphasisRe.lastIndex > 1) {
// Back up one character so RegExp has a chance to match the
// mis-matched marker (ex: "*text_*")
emphasisRe.lastIndex--;
}
}
if (emphasisIndex !== -1) {
pendingError = pendingError ||
handleRunEnd(line, lineIndex, 0, null, line.length, inTable);
// Adjust for pending run on new line
emphasisIndex = 0;
emphasisLength = 0;
}
}
);
}
}
};

@@ -5,3 +5,4 @@ // @ts-check

const { addError, forEachInlineChild } = require("../helpers");
const { addError } = require("../helpers");
const { filterByTypes } = require("../helpers/micromark.cjs");

@@ -13,8 +14,19 @@ module.exports = {

"function": function MD045(params, onError) {
forEachInlineChild(params, "image", function forToken(token) {
if (token.content === "") {
addError(onError, token.lineNumber);
const images = filterByTypes(params.parsers.micromark.tokens, [ "image" ]);
for (const image of images) {
const labelTexts = filterByTypes(image.children, [ "labelText" ]);
if (labelTexts.some((labelText) => labelText.text.length === 0)) {
const range = (image.startLine === image.endLine) ?
[ image.startColumn, image.endColumn - image.startColumn ] :
undefined;
addError(
onError,
image.startLine,
undefined,
undefined,
range
);
}
});
}
}
};

@@ -5,4 +5,5 @@ // @ts-check

const { addError, addErrorDetailIf, escapeForRegExp, filterTokens,
forEachInlineChild, forEachHeading, htmlElementRe } = require("../helpers");
const { addError, addErrorDetailIf } = require("../helpers");
const { filterByHtmlTokens, filterByTypes, getHtmlTagInfo } =
require("../helpers/micromark.cjs");

@@ -12,2 +13,3 @@ // Regular expression for identifying HTML anchor names

const nameRe = /\sname\s*=\s*['"]?([^'"\s>]+)/iu;
const anchorRe = /\{(#[a-z\d]+(?:[-_][a-z\d]+)*)\}/gu;

@@ -18,10 +20,10 @@ /**

*
* @param {Object} inline Inline token for heading.
* @param {Object} headingText Heading text token.
* @returns {string} Fragment string for heading.
*/
function convertHeadingToHTMLFragment(inline) {
const inlineText = inline.children
.filter((token) => token.type !== "html_inline")
.map((token) => token.content)
.join("");
function convertHeadingToHTMLFragment(headingText) {
const inlineText =
filterByTypes(headingText.children, [ "codeTextData", "data" ])
.map((token) => token.text)
.join("");
return "#" + encodeURIComponent(

@@ -47,6 +49,12 @@ inlineText

"function": function MD051(params, onError) {
const { tokens } = params.parsers.micromark;
const fragments = new Map();
// Process headings
forEachHeading(params, (heading, content, inline) => {
const fragment = convertHeadingToHTMLFragment(inline);
const headingTexts = filterByTypes(
tokens,
[ "atxHeadingText", "setextHeadingText" ]
);
for (const headingText of headingTexts) {
const fragment = convertHeadingToHTMLFragment(headingText);
const count = fragments.get(fragment) || 0;

@@ -57,10 +65,17 @@ if (count) {

fragments.set(fragment, count + 1);
});
let match = null;
while ((match = anchorRe.exec(headingText.text)) !== null) {
const [ , anchor ] = match;
if (!fragments.has(anchor)) {
fragments.set(anchor, 1);
}
}
}
// Process HTML anchors
const processHtmlToken = (token) => {
let match = null;
while ((match = htmlElementRe.exec(token.content)) !== null) {
const [ tag, , element ] = match;
const anchorMatch = idRe.exec(tag) ||
(element.toLowerCase() === "a" && nameRe.exec(tag));
for (const token of filterByHtmlTokens(tokens)) {
const htmlTagInfo = getHtmlTagInfo(token);
if (htmlTagInfo && !htmlTagInfo.close) {
const anchorMatch = idRe.exec(token.text) ||
(htmlTagInfo.name.toLowerCase() === "a" && nameRe.exec(token.text));
if (anchorMatch) {

@@ -70,56 +85,63 @@ fragments.set(`#${anchorMatch[1]}`, 0);

}
};
filterTokens(params, "html_block", processHtmlToken);
forEachInlineChild(params, "html_inline", processHtmlToken);
// Process link fragments
forEachInlineChild(params, "link_open", (token) => {
const { attrs, lineNumber, line } = token;
const href = attrs.find((attr) => attr[0] === "href");
const id = href && href[1];
if (id && (id.length > 1) && (id[0] === "#") && !fragments.has(id)) {
let context = id;
let range = null;
let fixInfo = null;
const match = line.match(
new RegExp(`\\[.*?\\]\\(${escapeForRegExp(context)}\\)`)
);
if (match) {
[ context ] = match;
const index = match.index;
const length = context.length;
range = [ index + 1, length ];
fixInfo = {
"editColumn": index + (length - id.length),
"deleteCount": id.length,
"insertText": null
};
}
// Process link and definition fragments
const parentChilds = [
[ "link", "resourceDestinationString" ],
[ "definition", "definitionDestinationString" ]
];
for (const [ parentType, definitionType ] of parentChilds) {
const links = filterByTypes(tokens, [ parentType ]);
for (const link of links) {
const definitions = filterByTypes(link.children, [ definitionType ]);
for (const definition of definitions) {
if (
(definition.text.length > 1) &&
definition.text.startsWith("#") &&
!fragments.has(definition.text)
) {
// eslint-disable-next-line no-undef-init
let context = undefined;
// eslint-disable-next-line no-undef-init
let range = undefined;
// eslint-disable-next-line no-undef-init
let fixInfo = undefined;
if (link.startLine === link.endLine) {
context = link.text;
range = [ link.startColumn, link.endColumn - link.startColumn ];
fixInfo = {
"editColumn": definition.startColumn,
"deleteCount": definition.endColumn - definition.startColumn
};
}
const definitionTextLower = definition.text.toLowerCase();
const mixedCaseKey = [ ...fragments.keys() ]
.find((key) => definitionTextLower === key.toLowerCase());
if (mixedCaseKey) {
// @ts-ignore
(fixInfo || {}).insertText = mixedCaseKey;
addErrorDetailIf(
onError,
link.startLine,
mixedCaseKey,
definition.text,
undefined,
context,
range,
fixInfo
);
} else {
addError(
onError,
link.startLine,
undefined,
context,
range
);
}
}
}
const idLower = id.toLowerCase();
const mixedCaseKey = [ ...fragments.keys() ]
.find((key) => idLower === key.toLowerCase());
if (mixedCaseKey) {
(fixInfo || {}).insertText = mixedCaseKey;
addErrorDetailIf(
onError,
lineNumber,
mixedCaseKey,
id,
undefined,
context,
range,
fixInfo
);
} else {
addError(
onError,
lineNumber,
undefined,
context,
// @ts-ignore
range
);
}
}
});
}
}
};
{
"name": "markdownlint",
"version": "0.29.0",
"version": "0.30.0",
"description": "A Node.js style checker and lint tool for Markdown/CommonMark files.",

@@ -36,6 +36,5 @@ "type": "commonjs",

"clone-test-repos-dotnet-docs": "cd test-repos && git clone https://github.com/dotnet/docs dotnet-docs --depth 1 --no-tags --quiet",
"clone-test-repos-electron-electron": "cd test-repos && git clone https://github.com/electron/electron electron-electron --depth 1 --no-tags --quiet && cd electron-electron && npm install --ignore-scripts @electron/lint-roller",
"clone-test-repos-electron-electron": "cd test-repos && git clone https://github.com/electron/electron electron-electron --depth 1 --no-tags --quiet && cd electron-electron && npm install --ignore-scripts @electron/lint-roller typescript@4",
"clone-test-repos-eslint-eslint": "cd test-repos && git clone https://github.com/eslint/eslint eslint-eslint --depth 1 --no-tags --quiet",
"clone-test-repos-mdn-content": "cd test-repos && git clone https://github.com/mdn/content mdn-content --depth 1 --no-tags --quiet",
"clone-test-repos-mdn-translated-content": "cd test-repos && git clone https://github.com/mdn/translated-content mdn-translated-content --depth 1 --no-tags --quiet",
"clone-test-repos-mkdocs-mkdocs": "cd test-repos && git clone https://github.com/mkdocs/mkdocs mkdocs-mkdocs --depth 1 --no-tags --quiet",

@@ -47,3 +46,3 @@ "clone-test-repos-mochajs-mocha": "cd test-repos && git clone https://github.com/mochajs/mocha mochajs-mocha --depth 1 --no-tags --quiet",

"clone-test-repos-webpack-webpack-js-org": "cd test-repos && git clone https://github.com/webpack/webpack.js.org webpack-webpack-js-org --depth 1 --no-tags --quiet",
"clone-test-repos": "mkdir test-repos && cd test-repos && npm run clone-test-repos-apache-airflow && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-electron-electron && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mdn-content && npm run clone-test-repos-mdn-translated-content && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-v8-v8-dev && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org",
"clone-test-repos": "mkdir test-repos && cd test-repos && npm run clone-test-repos-apache-airflow && npm run clone-test-repos-dotnet-docs && npm run clone-test-repos-electron-electron && npm run clone-test-repos-eslint-eslint && npm run clone-test-repos-mdn-content && npm run clone-test-repos-mkdocs-mkdocs && npm run clone-test-repos-mochajs-mocha && npm run clone-test-repos-pi-hole-docs && npm run clone-test-repos-v8-v8-dev && npm run clone-test-repos-webhintio-hint && npm run clone-test-repos-webpack-webpack-js-org",
"declaration": "npm run build-declaration && npm run test-declaration",

@@ -59,6 +58,7 @@ "example": "cd example && node standalone.js && grunt markdownlint --force && gulp markdownlint",

"test": "ava test/markdownlint-test.js test/markdownlint-test-config.js test/markdownlint-test-custom-rules.js test/markdownlint-test-helpers.js test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.js test/markdownlint-test-scenarios.js",
"test-cover": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --exclude 'test/**' --exclude 'micromark/**' npm test",
"test-cover": "c8 --100 npm test",
"test-declaration": "cd example/typescript && tsc && node type-check.js",
"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-micromark.mjs test/markdownlint-test-scenarios.js",
"update-snapshots-test-repos": "ava --timeout=10m --update-snapshots test/markdownlint-test-repos.js",
"upgrade": "npx --yes npm-check-updates --upgrade"

@@ -71,17 +71,19 @@ },

"markdown-it": "13.0.1",
"markdownlint-micromark": "0.1.5"
"markdownlint-micromark": "0.1.7"
},
"devDependencies": {
"@babel/core": "7.22.1",
"@babel/preset-env": "7.22.4",
"ava": "5.3.0",
"babel-loader": "9.1.2",
"c8": "7.14.0",
"eslint": "8.41.0",
"@babel/core": "7.22.10",
"@babel/preset-env": "7.22.10",
"ava": "5.3.1",
"babel-loader": "9.1.3",
"c8": "8.0.1",
"character-entities": "2.0.2",
"eslint": "8.46.0",
"eslint-plugin-es": "4.1.0",
"eslint-plugin-jsdoc": "46.1.0",
"eslint-plugin-n": "16.0.0",
"eslint-plugin-jsdoc": "46.4.6",
"eslint-plugin-n": "16.0.1",
"eslint-plugin-regexp": "1.15.0",
"eslint-plugin-unicorn": "47.0.0",
"globby": "13.1.4",
"eslint-plugin-unicorn": "48.0.1",
"gemoji": "8.1.0",
"globby": "13.2.2",
"js-yaml": "4.1.0",

@@ -91,12 +93,11 @@ "markdown-it-for-inline": "0.1.1",

"markdown-it-sup": "1.0.0",
"markdown-it-texmath": "1.0.0",
"markdownlint-rule-helpers": "0.19.0",
"markdownlint-rule-helpers": "0.20.0",
"npm-run-all": "4.1.5",
"strip-json-comments": "5.0.0",
"strip-json-comments": "5.0.1",
"terser-webpack-plugin": "5.3.9",
"toml": "3.0.0",
"tv4": "1.3.0",
"typescript": "5.1.3",
"webpack": "5.85.0",
"webpack-cli": "5.1.1",
"typescript": "5.1.6",
"webpack": "5.88.2",
"webpack-cli": "5.1.4",
"yaml": "2.3.1"

@@ -103,0 +104,0 @@ },

@@ -364,4 +364,10 @@ {

"description": "Blank lines above heading",
"type": "integer",
"minimum": 0,
"type": [
"integer",
"array"
],
"items": {
"type": "integer"
},
"minimum": -1,
"default": 1

@@ -371,4 +377,10 @@ },

"description": "Blank lines below heading",
"type": "integer",
"minimum": 0,
"type": [
"integer",
"array"
],
"items": {
"type": "integer"
},
"minimum": -1,
"default": 1

@@ -461,3 +473,3 @@ }

"punctuation": {
"description": "Punctuation characters not allowed at end of headings",
"description": "Punctuation characters",
"type": "string",

@@ -882,3 +894,3 @@ "default": ".,;:!。,;:!"

"style": {
"description": "Emphasis style should be consistent",
"description": "Emphasis style",
"type": "string",

@@ -907,3 +919,3 @@ "enum": [

"style": {
"description": "Strong style should be consistent",
"description": "Strong style",
"type": "string",

@@ -910,0 +922,0 @@ "enum": [

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

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