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

markdownlint-rule-helpers

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

markdownlint-rule-helpers - npm Package Compare versions

Comparing version 0.26.0 to 0.27.0

micromark-helpers.cjs

434

helpers.js

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

const micromark = require("./micromark.cjs");
const micromark = require("./micromark-helpers.cjs");

@@ -13,5 +13,8 @@ const { newLineRe, nextLinesRe } = require("./shared.js");

/** @typedef {import("../lib/markdownlint.js").RuleOnError} RuleOnError */
/** @typedef {import("../lib/markdownlint.js").RuleOnErrorFixInfo} RuleOnErrorFixInfo */
// Regular expression for matching common front matter (YAML and TOML)
module.exports.frontMatterRe =
/((^---\s*$[\s\S]+?^---\s*)|(^\+\+\+\s*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{\s*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m;
/((^---[^\S\r\n\u2028\u2029]*$[\s\S]+?^---\s*)|(^\+\+\+[^\S\r\n\u2028\u2029]*$[\s\S]+?^(\+\+\+|\.\.\.)\s*)|(^\{[^\S\r\n\u2028\u2029]*$[\s\S]+?^\}\s*))(\r\n|\r|\n|$)/m;

@@ -23,10 +26,2 @@ // Regular expression for matching the start of inline disable/enable comments

// Regular expression for blockquote prefixes
const blockquotePrefixRe = /^[>\s]*/;
module.exports.blockquotePrefixRe = blockquotePrefixRe;
// Regular expression for link reference definitions
const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])\]:/;
module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
// Regular expression for identifying an HTML entity at the end of a line

@@ -230,95 +225,2 @@ module.exports.endOfLineHtmlEntityRe =

/**
* Return the string representation of a fence markup character.
*
* @param {string} markup Fence string.
* @returns {string} String representation.
*/
module.exports.fencedCodeBlockStyleFor =
function fencedCodeBlockStyleFor(markup) {
switch (markup[0]) {
case "~":
return "tilde";
default:
return "backtick";
}
};
/**
* Return the string representation of a emphasis or strong markup character.
*
* @param {string} markup Emphasis or strong string.
* @returns {"asterisk" | "underscore"} String representation.
*/
module.exports.emphasisOrStrongStyleFor =
function emphasisOrStrongStyleFor(markup) {
switch (markup[0]) {
case "*":
return "asterisk";
default:
return "underscore";
}
};
/**
* @callback InlineCodeSpanCallback
* @param {string} code Code content.
* @param {number} lineIndex Line index (0-based).
* @param {number} columnIndex Column index (0-based).
* @param {number} ticks Count of backticks.
* @returns {void}
*/
/**
* Calls the provided function for each inline code span's content.
*
* @param {string} input Markdown content.
* @param {InlineCodeSpanCallback} handler Callback function taking (code,
* lineIndex, columnIndex, ticks).
* @returns {void}
*/
function forEachInlineCodeSpan(input, handler) {
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.slice(startIndex + startLength, endIndex),
lineIndex,
columnIndex,
startLength
);
i = j;
break;
}
}
}
}
}
module.exports.forEachInlineCodeSpan = forEachInlineCodeSpan;
/**
* Adds ellipsis to the left/right/middle of the specified text.

@@ -348,3 +250,3 @@ *

*
* @param {Object} onError RuleOnError instance.
* @param {RuleOnError} onError RuleOnError instance.
* @param {number} lineNumber Line number.

@@ -354,3 +256,3 @@ * @param {string} [detail] Error details.

* @param {number[]} [range] Column and length of error.
* @param {Object} [fixInfo] RuleOnErrorFixInfo instance.
* @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
* @returns {void}

@@ -372,3 +274,3 @@ */

*
* @param {Object} onError RuleOnError instance.
* @param {RuleOnError} onError RuleOnError instance.
* @param {number} lineNumber Line number.

@@ -380,3 +282,3 @@ * @param {Object} expected Expected value.

* @param {number[]} [range] Column and length of error.
* @param {Object} [fixInfo] RuleOnErrorFixInfo instance.
* @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
* @returns {void}

@@ -402,3 +304,3 @@ */

*
* @param {Object} onError RuleOnError instance.
* @param {RuleOnError} onError RuleOnError instance.
* @param {number} lineNumber Line number.

@@ -409,3 +311,3 @@ * @param {string} context Error context.

* @param {number[]} [range] Column and length of error.
* @param {Object} [fixInfo] RuleOnErrorFixInfo instance.
* @param {RuleOnErrorFixInfo} [fixInfo] RuleOnErrorFixInfo instance.
* @returns {void}

@@ -421,47 +323,39 @@ */

/**
* Adds an error object with context for a construct missing a blank line.
* Defines a range within a file (start line/column to end line/column, subset of MicromarkToken).
*
* @param {Object} onError RuleOnError instance.
* @param {string[]} lines Lines of Markdown content.
* @param {number} lineIndex Line index of line.
* @param {number} [lineNumber] Line number for override.
* @returns {void}
* @typedef {Object} FileRange
* @property {number} startLine Start line (1-based).
* @property {number} startColumn Start column (1-based).
* @property {number} endLine End line (1-based).
* @property {number} endColumn End column (1-based).
*/
function addErrorContextForLine(onError, lines, lineIndex, lineNumber) {
const line = lines[lineIndex];
// @ts-ignore
const quotePrefix = line.match(blockquotePrefixRe)[0].trimEnd();
addErrorContext(
onError,
lineIndex + 1,
line.trim(),
undefined,
undefined,
undefined,
{
lineNumber,
"insertText": `${quotePrefix}\n`
}
);
}
module.exports.addErrorContextForLine = addErrorContextForLine;
/**
* Determines whether the specified range is within another range.
* Returns whether line/column A is less than or equal to line/column B.
*
* @param {number[][]} ranges Array of ranges (line, index, length).
* @param {number} lineIndex Line index to check.
* @param {number} index Index to check.
* @param {number} length Length to check.
* @returns {boolean} True iff the specified range is within.
* @param {number} lineA Line A.
* @param {number} columnA Column A.
* @param {number} lineB Line B.
* @param {number} columnB Column B.
* @returns {boolean} True iff A is less than or equal to B.
*/
const withinAnyRange = (ranges, lineIndex, index, length) => (
!ranges.every((span) => (
(lineIndex !== span[0]) ||
(index < span[1]) ||
(index + length > span[1] + span[2])
))
const positionLessThanOrEqual = (lineA, columnA, lineB, columnB) => (
(lineA < lineB) ||
((lineA === lineB) && (columnA <= columnB))
);
module.exports.withinAnyRange = withinAnyRange;
/**
* Returns whether two ranges (or MicromarkTokens) overlap anywhere.
*
* @param {FileRange|import("../lib/markdownlint.js").MicromarkToken} rangeA Range A.
* @param {FileRange|import("../lib/markdownlint.js").MicromarkToken} rangeB Range B.
* @returns {boolean} True iff the two ranges overlap.
*/
module.exports.hasOverlap = function hasOverlap(rangeA, rangeB) {
const lte = positionLessThanOrEqual(rangeA.startLine, rangeA.startColumn, rangeB.startLine, rangeB.startColumn);
const first = lte ? rangeA : rangeB;
const second = lte ? rangeB : rangeA;
return positionLessThanOrEqual(second.startLine, second.startColumn, first.endLine, first.endColumn);
};
// Determines if the front matter includes a title

@@ -484,3 +378,3 @@ module.exports.frontMatterHasTitle =

*
* @param {import("../helpers/micromark.cjs").Token[]} tokens Micromark tokens.
* @param {import("../helpers/micromark-helpers.cjs").Token[]} tokens Micromark tokens.
* @returns {Object} Reference link/image data.

@@ -490,7 +384,19 @@ */

const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
const references = new Map();
const shortcuts = new Map();
const addReferenceToDictionary = (token, label, isShortcut) => {
const referenceDatum = [
token.startLine - 1,
token.startColumn - 1,
token.text.length
];
const reference = normalizeReference(label);
const dictionary = isShortcut ? shortcuts : references;
const referenceData = dictionary.get(reference) || [];
referenceData.push(referenceDatum);
dictionary.set(reference, referenceData);
};
const definitions = new Map();
const definitionLineIndices = [];
const duplicateDefinitions = [];
const references = new Map();
const shortcuts = new Map();
const filteredTokens =

@@ -505,3 +411,5 @@ micromark.filterByTypes(

// references and shortcuts
"gfmFootnoteCall", "image", "link"
"gfmFootnoteCall", "image", "link",
// undefined link labels
"undefinedReferenceCollapsed", "undefinedReferenceFull", "undefinedReferenceShortcut"
]

@@ -529,11 +437,6 @@ );

} else {
let destinationString = null;
const parent =
micromark.getTokenParentOfType(token, [ "definition" ]);
if (parent) {
destinationString = micromark.getTokenTextByType(
micromark.filterByPredicate(parent.children),
"definitionDestinationString"
);
}
micromark.getParentOfType(token, [ "definition" ]);
const destinationString = parent &&
micromark.getDescendantsByType(parent, [ "definitionDestination", "definitionDestinationRaw", "definitionDestinationString" ])[0]?.text;
definitions.set(

@@ -550,63 +453,34 @@ reference,

{
let isShortcut = false;
let isFullOrCollapsed = false;
let labelText = null;
let referenceStringText = null;
const shortcutCandidate =
micromark.matchAndGetTokensByType(token.children, [ "label" ]);
if (shortcutCandidate) {
labelText =
micromark.getTokenTextByType(
shortcutCandidate[0].children, "labelText"
);
isShortcut = (labelText !== null);
}
const fullAndCollapsedCandidate =
micromark.matchAndGetTokensByType(
token.children, [ "label", "reference" ]
// Identify if shortcut or full/collapsed
let isShortcut = (token.children.length === 1);
const isFullOrCollapsed = (token.children.length === 2) && !token.children.some((t) => t.type === "resource");
const [ labelText ] = micromark.getDescendantsByType(token, [ "label", "labelText" ]);
const [ referenceString ] = micromark.getDescendantsByType(token, [ "reference", "referenceString" ]);
let label = labelText?.text;
// Identify if footnote
if (!isShortcut && !isFullOrCollapsed) {
const [ footnoteCallMarker, footnoteCallString ] = token.children.filter(
(t) => [ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ].includes(t.type)
);
if (fullAndCollapsedCandidate) {
labelText =
micromark.getTokenTextByType(
fullAndCollapsedCandidate[0].children, "labelText"
);
referenceStringText =
micromark.getTokenTextByType(
fullAndCollapsedCandidate[1].children, "referenceString"
);
isFullOrCollapsed = (labelText !== null);
if (footnoteCallMarker && footnoteCallString) {
label = `${footnoteCallMarker.text}${footnoteCallString.text}`;
isShortcut = true;
}
}
const footnote = micromark.matchAndGetTokensByType(
token.children,
[
"gfmFootnoteCallLabelMarker", "gfmFootnoteCallMarker",
"gfmFootnoteCallString", "gfmFootnoteCallLabelMarker"
],
[ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ]
);
if (footnote) {
const callMarkerText = footnote[0].text;
const callString = footnote[1].text;
labelText = `${callMarkerText}${callString}`;
isShortcut = true;
}
// Track shortcuts separately due to ambiguity in "text [text] text"
// Track link (handle shortcuts separately due to ambiguity in "text [text] text")
if (isShortcut || isFullOrCollapsed) {
const referenceDatum = [
token.startLine - 1,
token.startColumn - 1,
token.text.length,
// @ts-ignore
labelText.length,
(referenceStringText || "").length
];
const reference =
normalizeReference(referenceStringText || labelText);
const dictionary = isShortcut ? shortcuts : references;
const referenceData = dictionary.get(reference) || [];
referenceData.push(referenceDatum);
dictionary.set(reference, referenceData);
addReferenceToDictionary(token, referenceString?.text || label, isShortcut);
}
}
break;
case "undefinedReferenceCollapsed":
case "undefinedReferenceFull":
case "undefinedReferenceShortcut":
{
const undefinedReference = micromark.getDescendantsByType(token, [ "undefinedReference" ])[0];
const label = undefinedReference.children.map((t) => t.text).join("");
const isShortcut = (token.type === "undefinedReferenceShortcut");
addReferenceToDictionary(token, label, isShortcut);
}
break;
}

@@ -665,116 +539,2 @@ }

/**
* Normalizes the fields of a RuleOnErrorFixInfo instance.
*
* @param {Object} fixInfo RuleOnErrorFixInfo instance.
* @param {number} [lineNumber] Line number.
* @returns {Object} Normalized RuleOnErrorFixInfo instance.
*/
function normalizeFixInfo(fixInfo, lineNumber) {
return {
"lineNumber": fixInfo.lineNumber || lineNumber,
"editColumn": fixInfo.editColumn || 1,
"deleteCount": fixInfo.deleteCount || 0,
"insertText": fixInfo.insertText || ""
};
}
/**
* Fixes the specified error on a line of Markdown content.
*
* @param {string} line Line of Markdown content.
* @param {Object} fixInfo RuleOnErrorFixInfo instance.
* @param {string} [lineEnding] Line ending to use.
* @returns {string | null} Fixed content.
*/
function applyFix(line, fixInfo, lineEnding) {
const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo);
const editIndex = editColumn - 1;
return (deleteCount === -1) ?
null :
line.slice(0, editIndex) +
insertText.replace(/\n/g, lineEnding || "\n") +
line.slice(editIndex + deleteCount);
}
module.exports.applyFix = applyFix;
/**
* 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("node:os"));
const lines = input.split(newLineRe);
// Normalize fixInfo objects
let fixInfos = errors
.filter((error) => error.fixInfo)
.map((error) => normalizeFixInfo(error.fixInfo, error.lineNumber));
// Sort bottom-to-top, line-deletes last, right-to-left, long-to-short
fixInfos.sort((a, b) => {
const aDeletingLine = (a.deleteCount === -1);
const bDeletingLine = (b.deleteCount === -1);
return (
(b.lineNumber - a.lineNumber) ||
(aDeletingLine ? 1 : (bDeletingLine ? -1 : 0)) ||
(b.editColumn - a.editColumn) ||
(b.insertText.length - a.insertText.length)
);
});
// Remove duplicate entries (needed for following collapse step)
let lastFixInfo = {};
fixInfos = fixInfos.filter((fixInfo) => {
const unique = (
(fixInfo.lineNumber !== lastFixInfo.lineNumber) ||
(fixInfo.editColumn !== lastFixInfo.editColumn) ||
(fixInfo.deleteCount !== lastFixInfo.deleteCount) ||
(fixInfo.insertText !== lastFixInfo.insertText)
);
lastFixInfo = fixInfo;
return unique;
});
// Collapse insert/no-delete and no-insert/delete for same line/column
lastFixInfo = {
"lineNumber": -1
};
for (const fixInfo of fixInfos) {
if (
(fixInfo.lineNumber === lastFixInfo.lineNumber) &&
(fixInfo.editColumn === lastFixInfo.editColumn) &&
!fixInfo.insertText &&
(fixInfo.deleteCount > 0) &&
lastFixInfo.insertText &&
!lastFixInfo.deleteCount) {
fixInfo.insertText = lastFixInfo.insertText;
lastFixInfo.lineNumber = 0;
}
lastFixInfo = fixInfo;
}
fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber);
// Apply all (remaining/updated) fixes
let lastLineIndex = -1;
let lastEditIndex = -1;
for (const fixInfo of fixInfos) {
const { lineNumber, editColumn, deleteCount } = fixInfo;
const lineIndex = lineNumber - 1;
const editIndex = editColumn - 1;
if (
(lineIndex !== lastLineIndex) ||
(deleteCount === -1) ||
((editIndex + deleteCount) <=
(lastEditIndex - ((deleteCount > 0) ? 0 : 1)))
) {
// @ts-ignore
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding);
}
lastLineIndex = lineIndex;
lastEditIndex = editIndex;
}
// Return corrected input
return lines.filter((line) => line !== null).join(lineEnding);
}
module.exports.applyFixes = applyFixes;
/**
* Expands a path with a tilde to an absolute path.

@@ -791,21 +551,1 @@ *

module.exports.expandTildePath = expandTildePath;
// Copied from markdownlint.js to avoid TypeScript compiler import() issue.
/**
* @typedef {Object} MarkdownItToken
* @property {string[][]} attrs HTML attributes.
* @property {boolean} block Block-level token.
* @property {MarkdownItToken[]} children Child nodes.
* @property {string} content Tag contents.
* @property {boolean} hidden Ignore element.
* @property {string} info Fence info.
* @property {number} level Nesting level.
* @property {number[]} map Beginning/ending line numbers.
* @property {string} markup Markup text.
* @property {Object} meta Arbitrary data.
* @property {number} nesting Level change.
* @property {string} tag HTML tag name.
* @property {string} type Token type.
* @property {number} lineNumber Line number (1-based).
* @property {string} line Line content.
*/
{
"name": "markdownlint-rule-helpers",
"version": "0.26.0",
"version": "0.27.0",
"description": "A collection of markdownlint helper functions for custom rules",
"main": "./helpers.js",
"exports": "./helpers.js",
"exports": {
".": "./helpers.js",
"./micromark": "./micromark-helpers.cjs"
},
"author": "David Anson (https://dlaa.me/)",

@@ -8,0 +11,0 @@ "license": "MIT",

@@ -20,18 +20,2 @@ # markdownlint-rule-helpers

## Examples
### Applying Recommended Fixes
```javascript
const { "sync": markdownlintSync } = require("markdownlint");
const markdownlintRuleHelpers = require("markdownlint-rule-helpers");
function fixMarkdownlintViolations(content) {
const fixResults = markdownlintSync({ strings: { content } });
return markdownlintRuleHelpers.applyFixes(content, fixResults.content);
}
```
See also: [`markdownlint` built-in rule implementations][lib].
## Tests

@@ -42,7 +26,6 @@

[custom-rules]: https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/CustomRules.md
[custom-rules]: https://github.com/DavidAnson/markdownlint/blob/v0.36.0/doc/CustomRules.md
[jsdoc]: https://en.m.wikipedia.org/wiki/JSDoc
[lib]: https://github.com/DavidAnson/markdownlint/tree/v0.35.0/lib
[markdown]: https://en.wikipedia.org/wiki/Markdown
[markdownlint]: https://github.com/DavidAnson/markdownlint
[rules]: https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/Rules.md
[rules]: https://github.com/DavidAnson/markdownlint/blob/v0.36.0/doc/Rules.md

@@ -5,2 +5,8 @@ // @ts-check

// Symbol for identifing the flat tokens array from micromark parse
module.exports.flatTokensSymbol = Symbol("flat-tokens");
// Symbol for identifying the htmlFlow token from micromark parse
module.exports.htmlFlowSymbol = Symbol("html-flow");
// Regular expression for matching common newline characters

@@ -7,0 +13,0 @@ // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js

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