Address new TypeScript warnings in core files, improve type definitions.
Some checks are pending
Checkers / linkcheck (push) Waiting to run
Checkers / spellcheck (push) Waiting to run
CI / build (20, macos-latest) (push) Waiting to run
CI / build (20, ubuntu-latest) (push) Waiting to run
CI / build (20, windows-latest) (push) Waiting to run
CI / build (22, macos-latest) (push) Waiting to run
CI / build (22, ubuntu-latest) (push) Waiting to run
CI / build (22, windows-latest) (push) Waiting to run
CI / build (24, macos-latest) (push) Waiting to run
CI / build (24, ubuntu-latest) (push) Waiting to run
CI / build (24, windows-latest) (push) Waiting to run
CI / pnpm (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
TestRepos / build (latest, ubuntu-latest) (push) Waiting to run
UpdateTestRepos / update (push) Waiting to run

This commit is contained in:
David Anson 2025-10-11 16:36:47 -07:00
parent bd02390014
commit 7beb9fc9d0
32 changed files with 354 additions and 170 deletions

View file

@ -54,6 +54,7 @@ export default [
"multiline-comment-style": [ "error", "separate-lines" ], "multiline-comment-style": [ "error", "separate-lines" ],
"no-empty-function": "off", "no-empty-function": "off",
"no-implicit-coercion": "off", "no-implicit-coercion": "off",
"no-inline-comments": [ "error", { "ignorePattern": " @type \\{.+\\} " } ],
"no-magic-numbers": "off", "no-magic-numbers": "off",
"no-param-reassign": "off", "no-param-reassign": "off",
"no-plusplus": "off", "no-plusplus": "off",

View file

@ -30,7 +30,7 @@ function assertLintResults(results: LintResults) {
assert.equal(results["string"][0].lineNumber, 1); assert.equal(results["string"][0].lineNumber, 1);
assert.deepEqual(results["string"][0].ruleNames, [ "MD047", "single-trailing-newline" ]); assert.deepEqual(results["string"][0].ruleNames, [ "MD047", "single-trailing-newline" ]);
assert.equal(results["string"][0].ruleDescription, "Files should end with a single newline character"); assert.equal(results["string"][0].ruleDescription, "Files should end with a single newline character");
assert.equal(results["string"][0].ruleInformation.replace(/v\d+\.\d+\.\d+/, "v0.0.0"), "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md047.md"); assert.equal(results["string"][0].ruleInformation?.replace(/v\d+\.\d+\.\d+/, "v0.0.0"), "https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md047.md");
assert.equal(results["string"][0].errorDetail, null); assert.equal(results["string"][0].errorDetail, null);
assert.equal(results["string"][0].errorContext, null); assert.equal(results["string"][0].errorContext, null);
assert.deepEqual(results["string"][0].errorRange, [ 9, 1 ]); assert.deepEqual(results["string"][0].errorRange, [ 9, 1 ]);

View file

@ -111,10 +111,11 @@ module.exports.cloneIfArray = cloneIfArray;
/** /**
* Clones the input if it is a URL. * Clones the input if it is a URL.
* *
* @param {Object} url Object of unknown type. * @param {Object | undefined} url Object of unknown type.
* @returns {Object} Clone of obj iff obj is a URL. * @returns {Object} Clone of obj iff obj is a URL.
*/ */
function cloneIfUrl(url) { function cloneIfUrl(url) {
// @ts-ignore
return isUrl(url) ? new URL(url) : url; return isUrl(url) ? new URL(url) : url;
} }
module.exports.cloneIfUrl = cloneIfUrl; module.exports.cloneIfUrl = cloneIfUrl;
@ -139,7 +140,7 @@ module.exports.getHtmlAttributeRe = function getHtmlAttributeRe(name) {
function isBlankLine(line) { function isBlankLine(line) {
const startComment = "<!--"; const startComment = "<!--";
const endComment = "-->"; const endComment = "-->";
const removeComments = (s) => { const removeComments = (/** @type {string} */ s) => {
while (true) { while (true) {
const start = s.indexOf(startComment); const start = s.indexOf(startComment);
const end = s.indexOf(endComment); const end = s.indexOf(endComment);
@ -177,8 +178,8 @@ const startsWithPipeRe = /^ *\|/;
const notCrLfRe = /[^\r\n]/g; const notCrLfRe = /[^\r\n]/g;
const notSpaceCrLfRe = /[^ \r\n]/g; const notSpaceCrLfRe = /[^ \r\n]/g;
const trailingSpaceRe = / +[\r\n]/g; const trailingSpaceRe = / +[\r\n]/g;
const replaceTrailingSpace = (s) => s.replace(notCrLfRe, safeCommentCharacter); const replaceTrailingSpace = (/** @type {string} */ s) => s.replace(notCrLfRe, safeCommentCharacter);
module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) { module.exports.clearHtmlCommentText = function clearHtmlCommentText(/** @type {string} */ text) {
let i = 0; let i = 0;
while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) { while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) {
const j = text.indexOf(htmlCommentEnd, i + 2); const j = text.indexOf(htmlCommentEnd, i + 2);
@ -220,7 +221,7 @@ module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) {
}; };
// Escapes a string for use in a RegExp // Escapes a string for use in a RegExp
module.exports.escapeForRegExp = function escapeForRegExp(str) { module.exports.escapeForRegExp = function escapeForRegExp(/** @type {string} */ str) {
return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
}; };
@ -355,7 +356,7 @@ module.exports.hasOverlap = function hasOverlap(rangeA, rangeB) {
// Determines if the front matter includes a title // Determines if the front matter includes a title
module.exports.frontMatterHasTitle = module.exports.frontMatterHasTitle =
function frontMatterHasTitle(frontMatterLines, frontMatterTitlePattern) { function frontMatterHasTitle(/** @type {readonly string[]} */ frontMatterLines, /** @type {string} */ frontMatterTitlePattern) {
const ignoreFrontMatter = const ignoreFrontMatter =
(frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern; (frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern;
const frontMatterTitleRe = const frontMatterTitleRe =
@ -367,18 +368,31 @@ module.exports.frontMatterHasTitle =
frontMatterLines.some((line) => frontMatterTitleRe.test(line)); frontMatterLines.some((line) => frontMatterTitleRe.test(line));
}; };
/**
* Result object for getReferenceLinkImageData.
*
* @typedef {Object} GetReferenceLinkImageDataResult
* @property {Map<string, number[][]>} references References.
* @property {Map<string, number[][]>} shortcuts Shortcuts.
* @property {Map<string, [number, string]>} definitions Definitions.
* @property {[string, number][]} duplicateDefinitions Duplicate definitions.
* @property {number[]} definitionLineIndices Definition line indices.
*/
/** /**
* Returns an object with information about reference links and images. * Returns an object with information about reference links and images.
* *
* @param {MicromarkToken[]} tokens Micromark tokens. * @param {MicromarkToken[]} tokens Micromark tokens.
* @returns {Object} Reference link/image data. * @returns {GetReferenceLinkImageDataResult} Reference link/image data.
*/ */
function getReferenceLinkImageData(tokens) { function getReferenceLinkImageData(tokens) {
const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); const normalizeReference = (/** @type {string} */ s) => s.toLowerCase().trim().replace(/\s+/g, " ");
const getText = (t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join(""); const getText = (/** @type {MicromarkToken} */ t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join("");
/** @type {Map<string, number[][]>} */
const references = new Map(); const references = new Map();
/** @type {Map<string, number[][]>} */
const shortcuts = new Map(); const shortcuts = new Map();
const addReferenceToDictionary = (token, label, isShortcut) => { const addReferenceToDictionary = (/** @type {MicromarkToken} */ token, /** @type {string} */ label, /** @type {boolean} */ isShortcut) => {
const referenceDatum = [ const referenceDatum = [
token.startLine - 1, token.startLine - 1,
token.startColumn - 1, token.startColumn - 1,
@ -390,8 +404,11 @@ function getReferenceLinkImageData(tokens) {
referenceData.push(referenceDatum); referenceData.push(referenceDatum);
dictionary.set(reference, referenceData); dictionary.set(reference, referenceData);
}; };
/** @type {Map<string, [number, string]>} */
const definitions = new Map(); const definitions = new Map();
/** @type {number[]} */
const definitionLineIndices = []; const definitionLineIndices = [];
/** @type {[string, number][]} */
const duplicateDefinitions = []; const duplicateDefinitions = [];
const filteredTokens = const filteredTokens =
micromark.filterByTypes( micromark.filterByTypes(
@ -433,7 +450,7 @@ function getReferenceLinkImageData(tokens) {
micromark.getDescendantsByType(parent, [ "definitionDestination", "definitionDestinationRaw", "definitionDestinationString" ])[0]?.text; micromark.getDescendantsByType(parent, [ "definitionDestination", "definitionDestinationRaw", "definitionDestinationString" ])[0]?.text;
definitions.set( definitions.set(
reference, reference,
[ token.startLine - 1, destinationString ] [ token.startLine - 1, destinationString || "" ]
); );
} }
} }
@ -490,7 +507,7 @@ module.exports.getReferenceLinkImageData = getReferenceLinkImageData;
* Gets the most common line ending, falling back to the platform default. * Gets the most common line ending, falling back to the platform default.
* *
* @param {string} input Markdown content to analyze. * @param {string} input Markdown content to analyze.
* @param {Object} [os] Node.js "os" module. * @param {{EOL: string}} [os] Node.js "os" module.
* @returns {string} Preferred line ending. * @returns {string} Preferred line ending.
*/ */
function getPreferredLineEnding(input, os) { function getPreferredLineEnding(input, os) {
@ -530,7 +547,7 @@ module.exports.getPreferredLineEnding = getPreferredLineEnding;
* Expands a path with a tilde to an absolute path. * Expands a path with a tilde to an absolute path.
* *
* @param {string} file Path that may begin with a tilde. * @param {string} file Path that may begin with a tilde.
* @param {Object} os Node.js "os" module. * @param {{homedir: () => string}} os Node.js "os" module.
* @returns {string} Absolute path (or original path). * @returns {string} Absolute path (or original path).
*/ */
function expandTildePath(file, os) { function expandTildePath(file, os) {
@ -591,6 +608,7 @@ function convertLintErrorsVersion2To1(errors) {
* @returns {LintErrors} Lint errors (v0). * @returns {LintErrors} Lint errors (v0).
*/ */
function convertLintErrorsVersion2To0(errors) { function convertLintErrorsVersion2To0(errors) {
/** @type {Object.<string, number[]>} */
const dictionary = {}; const dictionary = {};
for (const error of errors) { for (const error of errors) {
const ruleName = error.ruleNames[0]; const ruleName = error.ruleNames[0];
@ -606,10 +624,11 @@ function convertLintErrorsVersion2To0(errors) {
* Copies and transforms lint results from resultVersion 3 to ?. * Copies and transforms lint results from resultVersion 3 to ?.
* *
* @param {LintResults} results Lint results (v3). * @param {LintResults} results Lint results (v3).
* @param {(LintErrors) => LintErrors} transform Lint errors (v?). * @param {(errors: LintErrors) => LintErrors} transform Lint errors (v?).
* @returns {LintResults} Lint results (v?). * @returns {LintResults} Lint results (v?).
*/ */
function copyAndTransformResults(results, transform) { function copyAndTransformResults(results, transform) {
/** @type {Object.<string, LintErrors>} */
const newResults = {}; const newResults = {};
Object.defineProperty(newResults, "toString", { "value": results.toString }); Object.defineProperty(newResults, "toString", { "value": results.toString });
for (const key of Object.keys(results)) { for (const key of Object.keys(results)) {

View file

@ -17,6 +17,7 @@ const { flatTokensSymbol, htmlFlowSymbol, newLineRe } = require("./shared.cjs");
* @returns {boolean} True iff the token is within an htmlFlow type. * @returns {boolean} True iff the token is within an htmlFlow type.
*/ */
function inHtmlFlow(token) { function inHtmlFlow(token) {
// @ts-ignore
return Boolean(token[htmlFlowSymbol]); return Boolean(token[htmlFlowSymbol]);
} }
@ -126,8 +127,11 @@ function filterByPredicate(tokens, allowed, transformChildren) {
* @returns {Token[]} Filtered tokens. * @returns {Token[]} Filtered tokens.
*/ */
function filterByTypes(tokens, types, htmlFlow) { function filterByTypes(tokens, types, htmlFlow) {
const predicate = (token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token)); const predicate = (/** @type {Token} */ token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token));
const flatTokens = tokens[flatTokensSymbol]; /** @type {Token[]} */
const flatTokens =
// @ts-ignore
tokens[flatTokensSymbol];
if (flatTokens) { if (flatTokens) {
return flatTokens.filter(predicate); return flatTokens.filter(predicate);
} }
@ -163,7 +167,7 @@ function getBlockQuotePrefixText(tokens, lineNumber, count = 1) {
function getDescendantsByType(parent, typePath) { function getDescendantsByType(parent, typePath) {
let tokens = Array.isArray(parent) ? parent : [ parent ]; let tokens = Array.isArray(parent) ? parent : [ parent ];
for (const type of typePath) { for (const type of typePath) {
const predicate = (token) => Array.isArray(type) ? type.includes(token.type) : (type === token.type); const predicate = (/** @type {Token} */ token) => Array.isArray(type) ? type.includes(token.type) : (type === token.type);
tokens = tokens.flatMap((t) => t.children.filter(predicate)); tokens = tokens.flatMap((t) => t.children.filter(predicate));
} }
return tokens; return tokens;

View file

@ -6,6 +6,7 @@ import { filterByTypes } from "../helpers/micromark-helpers.cjs";
/** @typedef {import("markdownlint").RuleParams} RuleParams */ /** @typedef {import("markdownlint").RuleParams} RuleParams */
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */ /** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */
/** @typedef {import("../helpers/helpers.cjs").GetReferenceLinkImageDataResult} GetReferenceLinkImageDataResult */
/** @type {Map<string, object>} */ /** @type {Map<string, object>} */
const map = new Map(); const map = new Map();
@ -68,9 +69,10 @@ export function filterByTypesCached(types, htmlFlow) {
/** /**
* Gets a reference link and image data object. * Gets a reference link and image data object.
* *
* @returns {Object} Reference link and image data object. * @returns {GetReferenceLinkImageDataResult} Reference link and image data object.
*/ */
export function getReferenceLinkImageData() { export function getReferenceLinkImageData() {
// @ts-ignore
return getCached( return getCached(
getReferenceLinkImageData.name, getReferenceLinkImageData.name,
() => helpersGetReferenceLinkImageData(micromarkTokens()) () => helpersGetReferenceLinkImageData(micromarkTokens())

View file

@ -1,5 +1,6 @@
// @ts-check // @ts-check
/** @type {string[]} */
export const deprecatedRuleNames = []; export const deprecatedRuleNames = [];
export const fixableRuleNames = [ export const fixableRuleNames = [
"MD004", "MD005", "MD007", "MD009", "MD010", "MD011", "MD004", "MD005", "MD007", "MD009", "MD010", "MD011",

View file

@ -127,6 +127,7 @@ function annotateAndFreezeTokens(tokens, lines) {
} }
// Annotate children with lineNumber // Annotate children with lineNumber
if (token.children) { if (token.children) {
/** @type {number[]} */
const codeSpanExtraLines = []; const codeSpanExtraLines = [];
if (token.children.some((child) => child.type === "code_inline")) { if (token.children.some((child) => child.type === "code_inline")) {
forEachInlineCodeSpan(token.content, (code) => { forEachInlineCodeSpan(token.content, (code) => {
@ -140,7 +141,7 @@ function annotateAndFreezeTokens(tokens, lines) {
if ((child.type === "softbreak") || (child.type === "hardbreak")) { if ((child.type === "softbreak") || (child.type === "hardbreak")) {
lineNumber++; lineNumber++;
} else if (child.type === "code_inline") { } else if (child.type === "code_inline") {
lineNumber += codeSpanExtraLines.shift(); lineNumber += codeSpanExtraLines.shift() || 0;
} }
} }
} }

View file

@ -26,39 +26,38 @@ export function lintSync(options: Options | null): LintResults;
* @param {Configuration} config Configuration object. * @param {Configuration} config Configuration object.
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | undefined} parsers Parsing function(s). * @param {ConfigurationParser[] | undefined} parsers Parsing function(s).
* @param {Object} fs File system implementation. * @param {FsLike} fs File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
export function extendConfigPromise(config: Configuration, file: string, parsers: ConfigurationParser[] | undefined, fs: any): Promise<Configuration>; export function extendConfigPromise(config: Configuration, file: string, parsers: ConfigurationParser[] | undefined, fs: FsLike): Promise<Configuration>;
/** /**
* Read specified configuration file. * Read specified configuration file.
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing function(s).
* function(s). * @param {FsLike | ReadConfigCallback} [fs] File system implementation.
* @param {Object} [fs] File system implementation.
* @param {ReadConfigCallback} [callback] Callback (err, result) function. * @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
export function readConfigAsync(file: string, parsers?: ConfigurationParser[] | ReadConfigCallback, fs?: any, callback?: ReadConfigCallback): void; export function readConfigAsync(file: string, parsers?: ConfigurationParser[] | ReadConfigCallback, fs?: FsLike | ReadConfigCallback, callback?: ReadConfigCallback): void;
/** /**
* Read specified configuration file. * Read specified configuration file.
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation. * @param {FsLike} [fs] File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
export function readConfigPromise(file: string, parsers?: ConfigurationParser[], fs?: any): Promise<Configuration>; export function readConfigPromise(file: string, parsers?: ConfigurationParser[], fs?: FsLike): Promise<Configuration>;
/** /**
* Read specified configuration file. * Read specified configuration file.
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation. * @param {FsLike} [fs] File system implementation.
* @returns {Configuration} Configuration object. * @returns {Configuration} Configuration object.
*/ */
export function readConfigSync(file: string, parsers?: ConfigurationParser[], fs?: any): Configuration; export function readConfigSync(file: string, parsers?: ConfigurationParser[], fs?: FsLike): Configuration;
/** /**
* Applies the specified fix to a Markdown content line. * Applies the specified fix to a Markdown content line.
* *
@ -82,6 +81,19 @@ export function applyFixes(input: string, errors: LintError[]): string;
* @returns {string} SemVer string. * @returns {string} SemVer string.
*/ */
export function getVersion(): string; export function getVersion(): string;
/**
* Result object for removeFrontMatter.
*/
export type RemoveFrontMatterResult = {
/**
* Markdown content.
*/
content: string;
/**
* Front matter lines.
*/
frontMatterLines: string[];
};
/** /**
* Result object for getEffectiveConfig. * Result object for getEffectiveConfig.
*/ */
@ -120,6 +132,27 @@ export type EnabledRulesPerLineNumberResult = {
*/ */
rulesSeverity: Map<string, "error" | "warning">; rulesSeverity: Map<string, "error" | "warning">;
}; };
/**
* Node fs instance (or compatible object).
*/
export type FsLike = {
/**
* access method.
*/
access: (path: string, callback: (err: Error) => void) => void;
/**
* accessSync method.
*/
accessSync: (path: string) => void;
/**
* readFile method.
*/
readFile: (path: string, encoding: string, callback: (err: Error, data: string) => void) => void;
/**
* readFileSync method.
*/
readFileSync: (path: string, encoding: string) => string;
};
/** /**
* Function to implement rule logic. * Function to implement rule logic.
*/ */
@ -418,7 +451,7 @@ export type Options = {
/** /**
* File system implementation. * File system implementation.
*/ */
fs?: any; fs?: FsLike;
/** /**
* True to catch exceptions. * True to catch exceptions.
*/ */
@ -467,15 +500,15 @@ export type LintError = {
/** /**
* Link to more information. * Link to more information.
*/ */
ruleInformation: string; ruleInformation: string | null;
/** /**
* Detail about the error. * Detail about the error.
*/ */
errorDetail: string; errorDetail: string | null;
/** /**
* Context for the error. * Context for the error.
*/ */
errorContext: string; errorContext: string | null;
/** /**
* Column number (1-based) and length. * Column number (1-based) and length.
*/ */

View file

@ -24,15 +24,17 @@ function validateRuleList(ruleList, synchronous) {
// No need to validate if only using built-in rules // No need to validate if only using built-in rules
return result; return result;
} }
/** @type {Object.<string, boolean>} */
const allIds = {}; const allIds = {};
for (const [ index, rule ] of ruleList.entries()) { for (const [ index, rule ] of ruleList.entries()) {
const customIndex = index - rules.length; const customIndex = index - rules.length;
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/reject-any-type, jsdoc/require-jsdoc
function newError(property, value) { function newError(/** @type {string} */ property, /** @type {any} */ value) {
return new Error( return new Error(
`Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`); `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`);
} }
for (const property of [ "names", "tags" ]) { for (const property of [ "names", "tags" ]) {
// @ts-ignore
const value = rule[property]; const value = rule[property];
if (!result && if (!result &&
(!value || !Array.isArray(value) || (value.length === 0) || (!value || !Array.isArray(value) || (value.length === 0) ||
@ -45,6 +47,7 @@ function validateRuleList(ruleList, synchronous) {
[ "function", "function" ] [ "function", "function" ]
]) { ]) {
const property = propertyInfo[0]; const property = propertyInfo[0];
// @ts-ignore
const value = rule[property]; const value = rule[property];
if (!result && (!value || (typeof value !== propertyInfo[1]))) { if (!result && (!value || (typeof value !== propertyInfo[1]))) {
result = newError(property, value); result = newError(property, value);
@ -113,10 +116,12 @@ function newResults(ruleList) {
* *
* @param {boolean} useAlias True if rule alias should be used instead of name. * @param {boolean} useAlias True if rule alias should be used instead of name.
* @returns {string} String representation of the instance. * @returns {string} String representation of the instance.
* @this {LintResults}
*/ */
function toString(useAlias) { function toString(useAlias) {
// eslint-disable-next-line consistent-this, no-invalid-this, unicorn/no-this-assignment // eslint-disable-next-line consistent-this, unicorn/no-this-assignment
const lintResults = this; const lintResults = this;
/** @type {Object.<string, Rule> | null} */
let ruleNameToRule = null; let ruleNameToRule = null;
const results = []; const results = [];
const keys = Object.keys(lintResults); const keys = Object.keys(lintResults);
@ -127,6 +132,7 @@ function newResults(ruleList) {
for (const result of fileResults) { for (const result of fileResults) {
const ruleMoniker = result.ruleNames ? const ruleMoniker = result.ruleNames ?
result.ruleNames.join("/") : result.ruleNames.join("/") :
// @ts-ignore
(result.ruleName + "/" + result.ruleAlias); (result.ruleName + "/" + result.ruleAlias);
results.push( results.push(
file + ": " + file + ": " +
@ -173,14 +179,23 @@ function newResults(ruleList) {
return lintResults; return lintResults;
} }
/**
* Result object for removeFrontMatter.
*
* @typedef {Object} RemoveFrontMatterResult
* @property {string} content Markdown content.
* @property {string[]} frontMatterLines Front matter lines.
*/
/** /**
* Remove front matter (if present at beginning of content). * Remove front matter (if present at beginning of content).
* *
* @param {string} content Markdown content. * @param {string} content Markdown content.
* @param {RegExp | null} frontMatter Regular expression to match front matter. * @param {RegExp | null} frontMatter Regular expression to match front matter.
* @returns {Object} Trimmed content and front matter lines. * @returns {RemoveFrontMatterResult} Trimmed content and front matter lines.
*/ */
function removeFrontMatter(content, frontMatter) { function removeFrontMatter(content, frontMatter) {
/** @type {string[]} */
let frontMatterLines = []; let frontMatterLines = [];
if (frontMatter) { if (frontMatter) {
const frontMatterMatch = content.match(frontMatter); const frontMatterMatch = content.match(frontMatter);
@ -207,6 +222,7 @@ function removeFrontMatter(content, frontMatter) {
* @returns {Object.<string, string[]>} Map of alias to rule name. * @returns {Object.<string, string[]>} Map of alias to rule name.
*/ */
function mapAliasToRuleNames(ruleList) { function mapAliasToRuleNames(ruleList) {
/** @type {Object.<string, string[]>} */
const aliasToRuleNames = {}; const aliasToRuleNames = {};
// const tagToRuleNames = {}; // const tagToRuleNames = {};
for (const rule of ruleList) { for (const rule of ruleList) {
@ -357,7 +373,7 @@ function getEnabledRulesPerLineNumber(
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
// Helper functions // Helper functions
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function handleInlineConfig(input, forEachMatch, forEachLine) { function handleInlineConfig(/** @type {string[]} */ input, /** @type {(act: string, par: string, ind: number) => void} */ forEachMatch, /** @type {(() => void)|undefined} */ forEachLine = undefined) {
for (const [ lineIndex, line ] of input.entries()) { for (const [ lineIndex, line ] of input.entries()) {
if (!noInlineConfig) { if (!noInlineConfig) {
let match = null; let match = null;
@ -378,7 +394,7 @@ function getEnabledRulesPerLineNumber(
} }
} }
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function configureFile(action, parameter) { function configureFile(/** @type {string} */ action, /** @type {string} */ parameter) {
if (action === "CONFIGURE-FILE") { if (action === "CONFIGURE-FILE") {
const { "config": parsed } = parseConfiguration( const { "config": parsed } = parseConfiguration(
"CONFIGURE-FILE", parameter, configParsers "CONFIGURE-FILE", parameter, configParsers
@ -392,7 +408,7 @@ function getEnabledRulesPerLineNumber(
} }
} }
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function applyEnableDisable(action, parameter, state) { function applyEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {Map<string, boolean>} */ state) {
state = new Map(state); state = new Map(state);
const enabled = (action.startsWith("ENABLE")); const enabled = (action.startsWith("ENABLE"));
const trimmed = parameter && parameter.trim(); const trimmed = parameter && parameter.trim();
@ -406,13 +422,13 @@ function getEnabledRulesPerLineNumber(
return state; return state;
} }
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function enableDisableFile(action, parameter) { function enableDisableFile(/** @type {string} */ action, /** @type {string} */ parameter) {
if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) { if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
enabledRules = applyEnableDisable(action, parameter, enabledRules); enabledRules = applyEnableDisable(action, parameter, enabledRules);
} }
} }
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function captureRestoreEnableDisable(action, parameter) { function captureRestoreEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter) {
if (action === "CAPTURE") { if (action === "CAPTURE") {
capturedRules = enabledRules; capturedRules = enabledRules;
} else if (action === "RESTORE") { } else if (action === "RESTORE") {
@ -426,7 +442,7 @@ function getEnabledRulesPerLineNumber(
enabledRulesPerLineNumber.push(enabledRules); enabledRulesPerLineNumber.push(enabledRules);
} }
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function disableLineNextLine(action, parameter, lineNumber) { function disableLineNextLine(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {number} */ lineNumber) {
const disableLine = (action === "DISABLE-LINE"); const disableLine = (action === "DISABLE-LINE");
const disableNextLine = (action === "DISABLE-NEXT-LINE"); const disableNextLine = (action === "DISABLE-NEXT-LINE");
if (disableLine || disableNextLine) { if (disableLine || disableNextLine) {
@ -496,7 +512,8 @@ function lintContent(
synchronous, synchronous,
callback) { callback) {
// Provide a consistent error-reporting callback // Provide a consistent error-reporting callback
const callbackError = (error) => callback(error instanceof Error ? error : new Error(error)); // eslint-disable-next-line jsdoc/reject-any-type
const callbackError = (/** @type {any} */ error) => callback(error instanceof Error ? error : new Error(error));
// Remove UTF-8 byte order marker (if present) // Remove UTF-8 byte order marker (if present)
content = content.replace(/^\uFEFF/, ""); content = content.replace(/^\uFEFF/, "");
// Remove front matter // Remove front matter
@ -531,7 +548,7 @@ function lintContent(
// Parse content into lines and get markdown-it tokens // Parse content into lines and get markdown-it tokens
const lines = content.split(helpers.newLineRe); const lines = content.split(helpers.newLineRe);
// Function to run after fetching markdown-it tokens (when needed) // Function to run after fetching markdown-it tokens (when needed)
const lintContentInternal = (markdownitTokens) => { const lintContentInternal = (/** @type {MarkdownItToken[]} */ markdownitTokens) => {
// Create (frozen) parameters for rules // Create (frozen) parameters for rules
/** @type {MarkdownParsers} */ /** @type {MarkdownParsers} */
// @ts-ignore // @ts-ignore
@ -585,15 +602,17 @@ function lintContent(
...paramsBase, ...paramsBase,
...tokens, ...tokens,
parsers, parsers,
/** @type {RuleConfiguration} */
// @ts-ignore
"config": effectiveConfig[ruleName] "config": effectiveConfig[ruleName]
}); });
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function throwError(property) { function throwError(/** @type {string} */ property) {
throw new Error( throw new Error(
`Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`); `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`);
} }
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function onError(errorInfo) { function onError(/** @type {RuleOnErrorInfo} */ errorInfo) {
if (!errorInfo || if (!errorInfo ||
!helpers.isNumber(errorInfo.lineNumber) || !helpers.isNumber(errorInfo.lineNumber) ||
(errorInfo.lineNumber < 1) || (errorInfo.lineNumber < 1) ||
@ -683,6 +702,7 @@ function lintContent(
}); });
} }
// Call (possibly external) rule function to report errors // Call (possibly external) rule function to report errors
// @ts-ignore
const catchCallsOnError = (error) => onError({ const catchCallsOnError = (error) => onError({
"lineNumber": 1, "lineNumber": 1,
"detail": `This rule threw an exception: ${error.message || error}` "detail": `This rule threw an exception: ${error.message || error}`
@ -772,7 +792,7 @@ function lintContent(
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules. * @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
* @param {boolean} noInlineConfig Whether to allow inline configuration. * @param {boolean} noInlineConfig Whether to allow inline configuration.
* @param {number} resultVersion Version of the LintResults object to return. * @param {number} resultVersion Version of the LintResults object to return.
* @param {Object} fs File system implementation. * @param {FsLike} fs File system implementation.
* @param {boolean} synchronous Whether to execute synchronously. * @param {boolean} synchronous Whether to execute synchronously.
* @param {LintContentCallback} callback Callback (err, result) function. * @param {LintContentCallback} callback Callback (err, result) function.
* @returns {void} * @returns {void}
@ -792,7 +812,7 @@ function lintFile(
synchronous, synchronous,
callback) { callback) {
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function lintContentWrapper(err, content) { function lintContentWrapper(/** @type {Error | null} */ err, /** @type {string} */ content) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
@ -832,6 +852,8 @@ function lintInput(options, synchronous, callback) {
// Normalize inputs // Normalize inputs
options = options || {}; options = options || {};
callback = callback || function noop() {}; callback = callback || function noop() {};
/** @type {Rule[]} */
// @ts-ignore
const customRuleList = const customRuleList =
[ options.customRules || [] ] [ options.customRules || [] ]
.flat() .flat()
@ -851,6 +873,7 @@ function lintInput(options, synchronous, callback) {
callback(ruleErr); callback(ruleErr);
return; return;
} }
/** @type {string[]} */
let files = []; let files = [];
if (Array.isArray(options.files)) { if (Array.isArray(options.files)) {
files = [ ...options.files ]; files = [ ...options.files ];
@ -866,11 +889,14 @@ function lintInput(options, synchronous, callback) {
options.frontMatter; options.frontMatter;
const handleRuleFailures = !!options.handleRuleFailures; const handleRuleFailures = !!options.handleRuleFailures;
const noInlineConfig = !!options.noInlineConfig; const noInlineConfig = !!options.noInlineConfig;
// @ts-ignore
// eslint-disable-next-line dot-notation // eslint-disable-next-line dot-notation
const resultVersion = (options["resultVersion"] === undefined) ? 3 : options["resultVersion"]; const resultVersion = (options["resultVersion"] === undefined) ? 3 : options["resultVersion"];
const markdownItFactory = const markdownItFactory =
options.markdownItFactory || options.markdownItFactory ||
(() => { throw new Error("The option 'markdownItFactory' was required (due to the option 'customRules' including a rule requiring the 'markdown-it' parser), but 'markdownItFactory' was not set."); }); (() => { throw new Error("The option 'markdownItFactory' was required (due to the option 'customRules' including a rule requiring the 'markdown-it' parser), but 'markdownItFactory' was not set."); });
/** @type {FsLike} */
// @ts-ignore
const fs = options.fs || nodeFs; const fs = options.fs || nodeFs;
const aliasToRuleNames = mapAliasToRuleNames(ruleList); const aliasToRuleNames = mapAliasToRuleNames(ruleList);
const results = newResults(ruleList); const results = newResults(ruleList);
@ -878,14 +904,16 @@ function lintInput(options, synchronous, callback) {
let concurrency = 0; let concurrency = 0;
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function lintWorker() { function lintWorker() {
let currentItem = null; /** @type {string | undefined} */
let currentItem = undefined;
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function lintWorkerCallback(err, result) { function lintWorkerCallback(/** @type {Error | null} */ err, /** @type {LintError[] | undefined} */ result) {
concurrency--; concurrency--;
if (err) { if (err) {
done = true; done = true;
return callback(err); return callback(err);
} }
// @ts-ignore
results[currentItem] = result; results[currentItem] = result;
if (!synchronous) { if (!synchronous) {
lintWorker(); lintWorker();
@ -894,10 +922,9 @@ function lintInput(options, synchronous, callback) {
} }
if (done) { if (done) {
// Abort for error or nothing left to do // Abort for error or nothing left to do
} else if (files.length > 0) { } else if ((currentItem = files.shift())) {
// Lint next file // Lint next file
concurrency++; concurrency++;
currentItem = files.shift();
lintFile( lintFile(
ruleList, ruleList,
aliasToRuleNames, aliasToRuleNames,
@ -1013,15 +1040,24 @@ export function lintSync(options) {
return results; return results;
} }
/**
* Node fs instance (or compatible object).
*
* @typedef FsLike
* @property {(path: string, callback: (err: Error) => void) => void} access access method.
* @property {(path: string) => void} accessSync accessSync method.
* @property {(path: string, encoding: string, callback: (err: Error, data: string) => void) => void} readFile readFile method.
* @property {(path: string, encoding: string) => string} readFileSync readFileSync method.
*/
/** /**
* Resolve referenced "extends" path in a configuration file * Resolve referenced "extends" path in a configuration file
* using path.resolve() with require.resolve() as a fallback. * using path.resolve() with require.resolve() as a fallback.
* *
* @param {string} configFile Configuration file name. * @param {string} configFile Configuration file name.
* @param {string} referenceId Referenced identifier to resolve. * @param {string} referenceId Referenced identifier to resolve.
* @param {Object} fs File system implementation. * @param {FsLike} fs File system implementation.
* @param {ResolveConfigExtendsCallback} callback Callback (err, result) * @param {ResolveConfigExtendsCallback} callback Callback (err, result) function.
* function.
* @returns {void} * @returns {void}
*/ */
function resolveConfigExtends(configFile, referenceId, fs, callback) { function resolveConfigExtends(configFile, referenceId, fs, callback) {
@ -1049,7 +1085,7 @@ function resolveConfigExtends(configFile, referenceId, fs, callback) {
* *
* @param {string} configFile Configuration file name. * @param {string} configFile Configuration file name.
* @param {string} referenceId Referenced identifier to resolve. * @param {string} referenceId Referenced identifier to resolve.
* @param {Object} fs File system implementation. * @param {FsLike} fs File system implementation.
* @returns {string} Resolved path to file. * @returns {string} Resolved path to file.
*/ */
function resolveConfigExtendsSync(configFile, referenceId, fs) { function resolveConfigExtendsSync(configFile, referenceId, fs) {
@ -1074,9 +1110,8 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) {
* *
* @param {Configuration} config Configuration object. * @param {Configuration} config Configuration object.
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | undefined} parsers Parsing * @param {ConfigurationParser[] | undefined} parsers Parsing function(s).
* function(s). * @param {FsLike} fs File system implementation.
* @param {Object} fs File system implementation.
* @param {ReadConfigCallback} callback Callback (err, result) function. * @param {ReadConfigCallback} callback Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
@ -1116,7 +1151,7 @@ function extendConfig(config, file, parsers, fs, callback) {
* @param {Configuration} config Configuration object. * @param {Configuration} config Configuration object.
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | undefined} parsers Parsing function(s). * @param {ConfigurationParser[] | undefined} parsers Parsing function(s).
* @param {Object} fs File system implementation. * @param {FsLike} fs File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
export function extendConfigPromise(config, file, parsers, fs) { export function extendConfigPromise(config, file, parsers, fs) {
@ -1135,16 +1170,17 @@ export function extendConfigPromise(config, file, parsers, fs) {
* Read specified configuration file. * Read specified configuration file.
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing function(s).
* function(s). * @param {FsLike | ReadConfigCallback} [fs] File system implementation.
* @param {Object} [fs] File system implementation.
* @param {ReadConfigCallback} [callback] Callback (err, result) function. * @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
export function readConfigAsync(file, parsers, fs, callback) { export function readConfigAsync(file, parsers, fs, callback) {
if (!callback) { if (!callback) {
if (fs) { if (fs) {
// @ts-ignore
callback = fs; callback = fs;
// @ts-ignore
fs = null; fs = null;
} else { } else {
// @ts-ignore // @ts-ignore
@ -1153,12 +1189,12 @@ export function readConfigAsync(file, parsers, fs, callback) {
parsers = null; parsers = null;
} }
} }
if (!fs) { /** @type {FsLike} */
fs = nodeFs; // @ts-ignore
} const fsLike = fs || nodeFs;
// Read file // Read file
file = helpers.expandTildePath(file, os); file = helpers.expandTildePath(file, os);
fs.readFile(file, "utf8", (err, content) => { fsLike.readFile(file, "utf8", (err, content) => {
if (err) { if (err) {
// @ts-ignore // @ts-ignore
return callback(err); return callback(err);
@ -1172,7 +1208,7 @@ export function readConfigAsync(file, parsers, fs, callback) {
} }
// Extend configuration // Extend configuration
// @ts-ignore // @ts-ignore
return extendConfig(config, file, parsers, fs, callback); return extendConfig(config, file, parsers, fsLike, callback);
}); });
} }
@ -1181,7 +1217,7 @@ export function readConfigAsync(file, parsers, fs, callback) {
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation. * @param {FsLike} [fs] File system implementation.
* @returns {Promise<Configuration>} Configuration object. * @returns {Promise<Configuration>} Configuration object.
*/ */
export function readConfigPromise(file, parsers, fs) { export function readConfigPromise(file, parsers, fs) {
@ -1201,16 +1237,16 @@ export function readConfigPromise(file, parsers, fs) {
* *
* @param {string} file Configuration file name. * @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @param {Object} [fs] File system implementation. * @param {FsLike} [fs] File system implementation.
* @returns {Configuration} Configuration object. * @returns {Configuration} Configuration object.
*/ */
export function readConfigSync(file, parsers, fs) { export function readConfigSync(file, parsers, fs) {
if (!fs) { /** @type {FsLike} */
fs = nodeFs; // @ts-ignore
} const fsLike = fs || nodeFs;
// Read file // Read file
file = helpers.expandTildePath(file, os); file = helpers.expandTildePath(file, os);
const content = fs.readFileSync(file, "utf8"); const content = fsLike.readFileSync(file, "utf8");
// Try to parse file // Try to parse file
const { config, message } = parseConfiguration(file, content, parsers); const { config, message } = parseConfiguration(file, content, parsers);
if (!config) { if (!config) {
@ -1224,7 +1260,7 @@ export function readConfigSync(file, parsers, fs) {
const resolvedExtends = resolveConfigExtendsSync( const resolvedExtends = resolveConfigExtendsSync(
file, file,
helpers.expandTildePath(configExtends, os), helpers.expandTildePath(configExtends, os),
fs fsLike
); );
return { return {
...readConfigSync(resolvedExtends, parsers, fs), ...readConfigSync(resolvedExtends, parsers, fs),
@ -1512,7 +1548,7 @@ export function getVersion() {
* @property {Rule[] | Rule} [customRules] Custom rules. * @property {Rule[] | Rule} [customRules] Custom rules.
* @property {string[] | string} [files] Files to lint. * @property {string[] | string} [files] Files to lint.
* @property {RegExp | null} [frontMatter] Front matter pattern. * @property {RegExp | null} [frontMatter] Front matter pattern.
* @property {Object} [fs] File system implementation. * @property {FsLike} [fs] File system implementation.
* @property {boolean} [handleRuleFailures] True to catch exceptions. * @property {boolean} [handleRuleFailures] True to catch exceptions.
* @property {MarkdownItFactory} [markdownItFactory] Function to create a markdown-it parser. * @property {MarkdownItFactory} [markdownItFactory] Function to create a markdown-it parser.
* @property {boolean} [noInlineConfig] True to ignore HTML directives. * @property {boolean} [noInlineConfig] True to ignore HTML directives.
@ -1522,7 +1558,7 @@ export function getVersion() {
/** /**
* A markdown-it plugin. * A markdown-it plugin.
* *
* @typedef {Array} Plugin * @typedef {Object[]} Plugin
*/ */
/** /**
@ -1538,11 +1574,11 @@ export function getVersion() {
* @property {number} lineNumber Line number (1-based). * @property {number} lineNumber Line number (1-based).
* @property {string[]} ruleNames Rule name(s). * @property {string[]} ruleNames Rule name(s).
* @property {string} ruleDescription Rule description. * @property {string} ruleDescription Rule description.
* @property {string} ruleInformation Link to more information. * @property {string | null} ruleInformation Link to more information.
* @property {string} errorDetail Detail about the error. * @property {string | null} errorDetail Detail about the error.
* @property {string} errorContext Context for the error. * @property {string | null} errorContext Context for the error.
* @property {number[]|null} errorRange Column number (1-based) and length. * @property {number[] | null} errorRange Column number (1-based) and length.
* @property {FixInfo|null} fixInfo Fix information. * @property {FixInfo | null} fixInfo Fix information.
* @property {"error" | "warning"} severity Severity of the error. * @property {"error" | "warning"} severity Severity of the error.
*/ */

View file

@ -4,21 +4,9 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs";
import { getDescendantsByType, getParentOfType } from "../helpers/micromark-helpers.cjs"; import { getDescendantsByType, getParentOfType } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
const markerToStyle = { const markerToStyle = (/** @type {string} */ marker) => (marker === "-") ? "dash" : ((marker === "+") ? "plus" : "asterisk");
"-": "dash", const styleToMarker = (/** @type {string} */ style) => (style === "dash") ? "-" : ((style === "plus") ? "+" : "*");
"+": "plus", const differentItemStyle = (/** @type {string} */ style) => (style === "dash") ? "plus" : ((style === "plus") ? "asterisk" : "dash");
"*": "asterisk"
};
const styleToMarker = {
"dash": "-",
"plus": "+",
"asterisk": "*"
};
const differentItemStyle = {
"dash": "plus",
"plus": "asterisk",
"asterisk": "dash"
};
const validStyles = new Set([ const validStyles = new Set([
"asterisk", "asterisk",
"consistent", "consistent",
@ -36,6 +24,7 @@ export default {
"function": function MD004(params, onError) { "function": function MD004(params, onError) {
const style = String(params.config.style || "consistent"); const style = String(params.config.style || "consistent");
let expectedStyle = validStyles.has(style) ? style : "dash"; let expectedStyle = validStyles.has(style) ? style : "dash";
/** @type {("asterisk"|"dash"|"plus")[]} */
const nestingStyles = []; const nestingStyles = [];
for (const listUnordered of filterByTypesCached([ "listUnordered" ])) { for (const listUnordered of filterByTypesCached([ "listUnordered" ])) {
let nesting = 0; let nesting = 0;
@ -49,12 +38,12 @@ export default {
} }
const listItemMarkers = getDescendantsByType(listUnordered, [ "listItemPrefix", "listItemMarker" ]); const listItemMarkers = getDescendantsByType(listUnordered, [ "listItemPrefix", "listItemMarker" ]);
for (const listItemMarker of listItemMarkers) { for (const listItemMarker of listItemMarkers) {
const itemStyle = markerToStyle[listItemMarker.text]; const itemStyle = markerToStyle(listItemMarker.text);
if (style === "sublist") { if (style === "sublist") {
if (!nestingStyles[nesting]) { if (!nestingStyles[nesting]) {
nestingStyles[nesting] = nestingStyles[nesting] =
(itemStyle === nestingStyles[nesting - 1]) ? (itemStyle === nestingStyles[nesting - 1]) ?
differentItemStyle[itemStyle] : differentItemStyle(itemStyle) :
itemStyle; itemStyle;
} }
expectedStyle = nestingStyles[nesting]; expectedStyle = nestingStyles[nesting];
@ -74,7 +63,7 @@ export default {
{ {
"editColumn": column, "editColumn": column,
"deleteCount": length, "deleteCount": length,
"insertText": styleToMarker[expectedStyle] "insertText": styleToMarker(expectedStyle)
} }
); );
} }

View file

@ -17,7 +17,7 @@ export default {
const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks;
const ignoreCodeLanguages = new Set( const ignoreCodeLanguages = new Set(
(params.config.ignore_code_languages || []) (params.config.ignore_code_languages || [])
.map((language) => language.toLowerCase()) .map((/** @type {void} */ language) => String(language).toLowerCase())
); );
const spacesPerTab = params.config.spaces_per_tab; const spacesPerTab = params.config.spaces_per_tab;
const spaceMultiplier = (spacesPerTab === undefined) ? const spaceMultiplier = (spacesPerTab === undefined) ?

View file

@ -4,15 +4,18 @@ import { addErrorDetailIf, isBlankLine } from "../helpers/helpers.cjs";
import { getBlockQuotePrefixText, getHeadingLevel } from "../helpers/micromark-helpers.cjs"; import { getBlockQuotePrefixText, getHeadingLevel } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
const defaultLines = 1; const defaultLines = 1;
const getLinesFunction = (linesParam) => { // eslint-disable-next-line jsdoc/reject-any-type
const getLinesFunction = (/** @type {any} */ linesParam) => {
if (Array.isArray(linesParam)) { if (Array.isArray(linesParam)) {
const linesArray = new Array(6).fill(defaultLines); const linesArray = new Array(6).fill(defaultLines);
for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) { for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) {
linesArray[index] = value; linesArray[index] = value;
} }
return (heading) => linesArray[getHeadingLevel(heading) - 1]; return (/** @type {MicromarkToken} */ heading) => linesArray[getHeadingLevel(heading) - 1];
} }
// Coerce linesParam to a number // Coerce linesParam to a number
const lines = (linesParam === undefined) ? defaultLines : Number(linesParam); const lines = (linesParam === undefined) ? defaultLines : Number(linesParam);

View file

@ -31,7 +31,7 @@ export default {
"tags": [ "ol" ], "tags": [ "ol" ],
"parser": "micromark", "parser": "micromark",
"function": function MD029(params, onError) { "function": function MD029(params, onError) {
const style = String(params.config.style || "one_or_ordered"); const style = String(params.config.style);
for (const listOrdered of filterByTypesCached([ "listOrdered" ])) { for (const listOrdered of filterByTypesCached([ "listOrdered" ])) {
const listItemPrefixes = getDescendantsByType(listOrdered, [ "listItemPrefix" ]); const listItemPrefixes = getDescendantsByType(listOrdered, [ "listItemPrefix" ]);
let expected = 1; let expected = 1;
@ -48,10 +48,10 @@ export default {
} }
} }
// Determine effective style // Determine effective style
let listStyle = style; const listStyle = ((style === "one") || (style === "ordered") || (style === "zero")) ?
if (listStyle === "one_or_ordered") { style :
listStyle = incrementing ? "ordered" : "one"; (incrementing ? "ordered" : "one");
} else if (listStyle === "zero") { if (listStyle === "zero") {
expected = 0; expected = 0;
} else if (listStyle === "one") { } else if (listStyle === "one") {
expected = 1; expected = 1;

View file

@ -4,7 +4,9 @@ import { addErrorContext, isBlankLine } from "../helpers/helpers.cjs";
import { filterByPredicate, getBlockQuotePrefixText, nonContentTokens } from "../helpers/micromark-helpers.cjs"; import { filterByPredicate, getBlockQuotePrefixText, nonContentTokens } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
const isList = (token) => ( /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
const isList = (/** @type {MicromarkToken} */ token) => (
(token.type === "listOrdered") || (token.type === "listUnordered") (token.type === "listOrdered") || (token.type === "listUnordered")
); );

View file

@ -4,6 +4,11 @@ import { addError, nextLinesRe } from "../helpers/helpers.cjs";
import { getHtmlTagInfo, getParentOfType } from "../helpers/micromark-helpers.cjs"; import { getHtmlTagInfo, getParentOfType } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
/** @typedef {import("micromark-extension-gfm-table")} */
// eslint-disable-next-line jsdoc/reject-any-type
const toLowerCaseStringArray = (/** @type {any} */ arr) => Array.isArray(arr) ? arr.map((elm) => String(elm).toLowerCase()) : [];
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
export default { export default {
"names": [ "MD033", "no-inline-html" ], "names": [ "MD033", "no-inline-html" ],
@ -11,35 +16,30 @@ export default {
"tags": [ "html" ], "tags": [ "html" ],
"parser": "micromark", "parser": "micromark",
"function": function MD033(params, onError) { "function": function MD033(params, onError) {
let allowedElements = params.config.allowed_elements; const allowedElements = toLowerCaseStringArray(params.config.allowed_elements);
allowedElements = Array.isArray(allowedElements) ? allowedElements : []; // If not defined, use allowedElements for backward compatibility
allowedElements = allowedElements.map((element) => element.toLowerCase()); const tableAllowedElements = toLowerCaseStringArray(params.config.table_allowed_elements || params.config.allowed_elements);
let tableAllowedElements = params.config.table_allowed_elements;
// if not defined, use allowedElements for backward compatibility
tableAllowedElements = Array.isArray(tableAllowedElements) ? tableAllowedElements : allowedElements;
tableAllowedElements = tableAllowedElements.map((element) => element.toLowerCase());
for (const token of filterByTypesCached([ "htmlText" ], true)) { for (const token of filterByTypesCached([ "htmlText" ], true)) {
const htmlTagInfo = getHtmlTagInfo(token); const htmlTagInfo = getHtmlTagInfo(token);
const elementName = htmlTagInfo?.name.toLowerCase(); if (htmlTagInfo && !htmlTagInfo.close) {
const inTable = !!getParentOfType(token, [ "table" ]); const elementName = htmlTagInfo?.name.toLowerCase();
if ( const inTable = !!getParentOfType(token, [ "table" ]);
htmlTagInfo && if (
!htmlTagInfo.close && !( (inTable || !allowedElements.includes(elementName)) &&
(!inTable && allowedElements.includes(elementName)) || (!inTable || !tableAllowedElements.includes(elementName))
(inTable && tableAllowedElements.includes(elementName)) ) {
) const range = [
) { token.startColumn,
const range = [ token.text.replace(nextLinesRe, "").length
token.startColumn, ];
token.text.replace(nextLinesRe, "").length addError(
]; onError,
addError( token.startLine,
onError, "Element: " + htmlTagInfo.name,
token.startLine, undefined,
"Element: " + htmlTagInfo.name, range
undefined, );
range }
);
} }
} }
} }

View file

@ -3,6 +3,7 @@
import { addErrorContext } from "../helpers/helpers.cjs"; import { addErrorContext } from "../helpers/helpers.cjs";
import { filterByPredicate, getHtmlTagInfo, inHtmlFlow } from "../helpers/micromark-helpers.cjs"; import { filterByPredicate, getHtmlTagInfo, inHtmlFlow } from "../helpers/micromark-helpers.cjs";
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @typedef {import("micromark-extension-gfm-autolink-literal")} */ /** @typedef {import("micromark-extension-gfm-autolink-literal")} */
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
@ -12,7 +13,7 @@ export default {
"tags": [ "links", "url" ], "tags": [ "links", "url" ],
"parser": "micromark", "parser": "micromark",
"function": function MD034(params, onError) { "function": function MD034(params, onError) {
const literalAutolinks = (tokens) => ( const literalAutolinks = (/** @type {MicromarkToken[]} */ tokens) => (
filterByPredicate( filterByPredicate(
tokens, tokens,
(token) => { (token) => {

View file

@ -4,14 +4,16 @@ import { addErrorContext, allPunctuation } from "../helpers/helpers.cjs";
import { getDescendantsByType } from "../helpers/micromark-helpers.cjs"; import { getDescendantsByType } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
/** @typedef {import("markdownlint").MicromarkTokenType} TokenType */ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @type {TokenType[][]} */ /** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */
/** @type {MicromarkTokenType[][]} */
const emphasisTypes = [ const emphasisTypes = [
[ "emphasis", "emphasisText" ], [ "emphasis", "emphasisText" ],
[ "strong", "strongText" ] [ "strong", "strongText" ]
]; ];
const isParagraphChildMeaningful = (token) => !( const isParagraphChildMeaningful = (/** @type {MicromarkToken} */ token) => !(
(token.type === "htmlText") || (token.type === "htmlText") ||
((token.type === "data") && (token.text.trim().length === 0)) ((token.type === "data") && (token.text.trim().length === 0))
); );

View file

@ -4,6 +4,8 @@ import { addErrorContext } from "../helpers/helpers.cjs";
import { getDescendantsByType } from "../helpers/micromark-helpers.cjs"; import { getDescendantsByType } from "../helpers/micromark-helpers.cjs";
import { getReferenceLinkImageData, filterByTypesCached } from "./cache.mjs"; import { getReferenceLinkImageData, filterByTypesCached } from "./cache.mjs";
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
export default { export default {
"names": [ "MD042", "no-empty-links" ], "names": [ "MD042", "no-empty-links" ],
@ -12,9 +14,9 @@ export default {
"parser": "micromark", "parser": "micromark",
"function": function MD042(params, onError) { "function": function MD042(params, onError) {
const { definitions } = getReferenceLinkImageData(); const { definitions } = getReferenceLinkImageData();
const isReferenceDefinitionHash = (token) => { const isReferenceDefinitionHash = (/** @type {MicromarkToken} */ token) => {
const definition = definitions.get(token.text.trim()); const definition = definitions.get(token.text.trim());
return (definition && (definition[1] === "#")); return Boolean(definition && (definition[1] === "#"));
}; };
const links = filterByTypesCached([ "link" ]); const links = filterByTypesCached([ "link" ]);
for (const link of links) { for (const link of links) {

View file

@ -21,8 +21,8 @@ export default {
let matchAny = false; let matchAny = false;
let hasError = false; let hasError = false;
let anyHeadings = false; let anyHeadings = false;
const getExpected = () => requiredHeadings[i++] || "[None]"; const getExpected = () => String(requiredHeadings[i++] || "[None]");
const handleCase = (str) => (matchCase ? str : str.toLowerCase()); const handleCase = (/** @type {string} */ str) => (matchCase ? str : str.toLowerCase());
for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) { for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) {
if (!hasError) { if (!hasError) {
const headingText = getHeadingText(heading); const headingText = getHeadingText(heading);

View file

@ -17,7 +17,7 @@ export default {
"function": function MD044(params, onError) { "function": function MD044(params, onError) {
let names = params.config.names; let names = params.config.names;
names = Array.isArray(names) ? names : []; names = Array.isArray(names) ? names : [];
names.sort((a, b) => (b.length - a.length) || a.localeCompare(b)); names.sort((/** @type {string} */ a, /** @type {string} */ b) => (b.length - a.length) || a.localeCompare(b));
if (names.length === 0) { if (names.length === 0) {
// Nothing to check; avoid doing any work // Nothing to check; avoid doing any work
return; return;

View file

@ -3,10 +3,9 @@
import { addErrorDetailIf } from "../helpers/helpers.cjs"; import { addErrorDetailIf } from "../helpers/helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
const tokenTypeToStyle = { /** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */
"codeFenced": "fenced",
"codeIndented": "indented" const tokenTypeToStyle = (/** @type {MicromarkTokenType} */ tokenType) => (tokenType === "codeFenced") ? "fenced" : "indented";
};
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
export default { export default {
@ -19,13 +18,14 @@ export default {
for (const token of filterByTypesCached([ "codeFenced", "codeIndented" ])) { for (const token of filterByTypesCached([ "codeFenced", "codeIndented" ])) {
const { startLine, type } = token; const { startLine, type } = token;
if (expectedStyle === "consistent") { if (expectedStyle === "consistent") {
expectedStyle = tokenTypeToStyle[type]; expectedStyle = tokenTypeToStyle(type);
} }
addErrorDetailIf( addErrorDetailIf(
onError, onError,
startLine, startLine,
expectedStyle, expectedStyle,
tokenTypeToStyle[type]); tokenTypeToStyle(type)
);
} }
} }
}; };

View file

@ -16,7 +16,7 @@ export default {
const lines = params.lines; const lines = params.lines;
const { references, shortcuts, definitions, duplicateDefinitions } = const { references, shortcuts, definitions, duplicateDefinitions } =
getReferenceLinkImageData(); getReferenceLinkImageData();
const singleLineDefinition = (line) => ( const singleLineDefinition = (/** @type {string} */ line) => (
line.replace(linkReferenceDefinitionRe, "").trim().length > 0 line.replace(linkReferenceDefinitionRe, "").trim().length > 0
); );
const deleteFixInfo = { const deleteFixInfo = {

View file

@ -5,9 +5,9 @@ import { getDescendantsByType } from "../helpers/micromark-helpers.cjs";
import { getReferenceLinkImageData, filterByTypesCached } from "./cache.mjs"; import { getReferenceLinkImageData, filterByTypesCached } from "./cache.mjs";
const backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g; const backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g;
const removeBackslashEscapes = (text) => text.replace(backslashEscapeRe, "$1"); const removeBackslashEscapes = (/** @type {string} **/ text) => text.replace(backslashEscapeRe, "$1");
const autolinkDisallowedRe = /[ <>]/; const autolinkDisallowedRe = /[ <>]/;
const autolinkAble = (destination) => { const autolinkAble = (/** @type {string} */ destination) => {
try { try {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new URL(destination); new URL(destination);
@ -73,9 +73,11 @@ export default {
const referenceString = getDescendantsByType(link, [ "reference", "referenceString" ])[0]?.text; const referenceString = getDescendantsByType(link, [ "reference", "referenceString" ])[0]?.text;
const isCollapsed = (referenceString === undefined); const isCollapsed = (referenceString === undefined);
const definition = definitions.get(referenceString || label); const definition = definitions.get(referenceString || label);
destination = definition && definition[1]; destination = (definition && definition[1]) || "";
isError = destination && isError = Boolean(
(isShortcut ? !shortcut : (isCollapsed ? !collapsed : !full)); destination &&
(isShortcut ? !shortcut : (isCollapsed ? !collapsed : !full))
);
} }
} }
if (isError) { if (isError) {

View file

@ -3,15 +3,16 @@
import { addErrorDetailIf } from "../helpers/helpers.cjs"; import { addErrorDetailIf } from "../helpers/helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */
/** @typedef {import("micromark-extension-gfm-table")} */
const whitespaceTypes = new Set([ "linePrefix", "whitespace" ]); const whitespaceTypes = new Set([ "linePrefix", "whitespace" ]);
const ignoreWhitespace = (tokens) => tokens.filter( const ignoreWhitespace = (/** @type {MicromarkToken[]} */ tokens) => tokens.filter(
(token) => !whitespaceTypes.has(token.type) (token) => !whitespaceTypes.has(token.type)
); );
const firstOrNothing = (items) => items[0]; const firstOrNothing = (/** @type {MicromarkToken[]} */ items) => items[0];
const lastOrNothing = (items) => items[items.length - 1]; const lastOrNothing = (/** @type {MicromarkToken[]} */ items) => items[items.length - 1];
const makeRange = (start, end) => [ start, end - start + 1 ]; const makeRange = (/** @type {number} */ start, /** @type {number} */ end) => [ start, end - start + 1 ];
/** @typedef {import("micromark-extension-gfm-table")} */
/** @type {import("markdownlint").Rule} */ /** @type {import("markdownlint").Rule} */
export default { export default {

View file

@ -4,7 +4,7 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs";
import { getParentOfType } from "../helpers/micromark-helpers.cjs"; import { getParentOfType } from "../helpers/micromark-helpers.cjs";
import { filterByTypesCached } from "./cache.mjs"; import { filterByTypesCached } from "./cache.mjs";
const makeRange = (start, end) => [ start, end - start + 1 ]; const makeRange = (/** @type {number} */ start, /** @type {number} */ end) => [ start, end - start + 1 ];
/** @typedef {import("micromark-extension-gfm-table")} */ /** @typedef {import("micromark-extension-gfm-table")} */

View file

@ -41,6 +41,7 @@ export default {
const headingRow = rows[0]; const headingRow = rows[0];
// Determine errors for style "aligned" // Determine errors for style "aligned"
/** @type {RuleOnErrorInfo[]} */
const errorsIfAligned = []; const errorsIfAligned = [];
if (styleAlignedAllowed) { if (styleAlignedAllowed) {
const headingDividerColumns = filterByTypes(headingRow.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn); const headingDividerColumns = filterByTypes(headingRow.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn);
@ -56,7 +57,9 @@ export default {
} }
// Determine errors for styles "compact" and "tight" // Determine errors for styles "compact" and "tight"
/** @type {RuleOnErrorInfo[]} */
const errorsIfCompact = []; const errorsIfCompact = [];
/** @type {RuleOnErrorInfo[]} */
const errorsIfTight = []; const errorsIfTight = [];
if ( if (
(styleCompactAllowed || styleTightAllowed) && (styleCompactAllowed || styleTightAllowed) &&

View file

@ -212,7 +212,9 @@ function parseInternal(
const events = getEvents(markdown, micromarkParseOptions); const events = getEvents(markdown, micromarkParseOptions);
// Create Token objects // Create Token objects
/** @type {MicromarkToken[]} */
const document = []; const document = [];
/** @type {MicromarkToken[]} */
let flatTokens = []; let flatTokens = [];
/** @type {MicromarkToken} */ /** @type {MicromarkToken} */
const root = { const root = {
@ -282,6 +284,7 @@ function parseInternal(
); );
current.children = tokens; current.children = tokens;
// Avoid stack overflow of Array.push(...spread) // Avoid stack overflow of Array.push(...spread)
// @ts-ignore
// eslint-disable-next-line unicorn/prefer-spread // eslint-disable-next-line unicorn/prefer-spread
flatTokens = flatTokens.concat(tokens[flatTokensSymbol]); flatTokens = flatTokens.concat(tokens[flatTokensSymbol]);
} }

View file

@ -6,8 +6,10 @@ const throwForSync = () => {
}; };
export const fs = { export const fs = {
// @ts-ignore
"access": (path, callback) => callback(getError()), "access": (path, callback) => callback(getError()),
"accessSync": throwForSync, "accessSync": throwForSync,
// @ts-ignore
"readFile": (path, options, callback) => callback(getError()), "readFile": (path, options, callback) => callback(getError()),
"readFileSync": throwForSync "readFileSync": throwForSync
}; };

View file

@ -4,7 +4,7 @@
* Result of a call to parseConfiguration. * Result of a call to parseConfiguration.
* *
* @typedef {Object} ParseConfigurationResult * @typedef {Object} ParseConfigurationResult
* @property {Object | null} config Configuration object if successful. * @property {import("markdownlint").Configuration | null} config Configuration object if successful.
* @property {string | null} message Error message if an error occurred. * @property {string | null} message Error message if an error occurred.
*/ */
@ -13,7 +13,7 @@
* *
* @param {string} name Name of the configuration file. * @param {string} name Name of the configuration file.
* @param {string} content Configuration content. * @param {string} content Configuration content.
* @param {import("./markdownlint.mjs").ConfigurationParser[]} [parsers] Parsing function(s). * @param {import("markdownlint").ConfigurationParser[]} [parsers] Parsing function(s).
* @returns {ParseConfigurationResult} Parse configuration result. * @returns {ParseConfigurationResult} Parse configuration result.
*/ */
export default function parseConfiguration(name, content, parsers) { export default function parseConfiguration(name, content, parsers) {
@ -28,8 +28,9 @@ export default function parseConfiguration(name, content, parsers) {
config = (result && (typeof result === "object") && !Array.isArray(result)) ? result : {}; config = (result && (typeof result === "object") && !Array.isArray(result)) ? result : {};
// Succeeded // Succeeded
return false; return false;
} catch(error) { // eslint-disable-next-line jsdoc/reject-any-type
errors.push(`Parser ${index++}: ${error.message}`); } catch(/** @type {any} */ error) {
errors.push(`Parser ${index++}: ${error?.message}`);
} }
// Failed, try the next parser // Failed, try the next parser
return true; return true;

View file

@ -60691,6 +60691,48 @@ Generated by [AVA](https://avajs.dev).
], ],
severity: 'error', severity: 'error',
}, },
{
errorContext: null,
errorDetail: 'Expected: plus; Actual: dash',
errorRange: [
13,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 13,
insertText: '+',
},
lineNumber: 66,
ruleDescription: 'Unordered list style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md004.md',
ruleNames: [
'MD004',
'ul-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: 'Expected: asterisk; Actual: plus',
errorRange: [
15,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 15,
insertText: '*',
},
lineNumber: 75,
ruleDescription: 'Unordered list style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md004.md',
ruleNames: [
'MD004',
'ul-style',
],
severity: 'error',
},
], ],
fixed: `# sublist-bullet-style␊ fixed: `# sublist-bullet-style␊
@ -60751,6 +60793,23 @@ Generated by [AVA](https://avajs.dev).
- item␊ - item␊
- item {MD004}␊ - item {MD004}␊
- item␊
* item␊
+ item␊
- item␊
* item␊
- item␊
+ item {MD004}␊
- item␊
* item␊
+ item␊
- item␊
* item␊
- item␊
+ item␊
* item {MD004}␊
<!-- markdownlint-configure-file {␊ <!-- markdownlint-configure-file {␊
"ul-style": {␊ "ul-style": {␊
"style": "sublist"␊ "style": "sublist"␊

View file

@ -57,6 +57,23 @@
- item - item
+ item {MD004} + item {MD004}
- item
* item
+ item
- item
* item
- item
- item {MD004}
- item
* item
+ item
- item
* item
- item
+ item
+ item {MD004}
<!-- markdownlint-configure-file { <!-- markdownlint-configure-file {
"ul-style": { "ul-style": {
"style": "sublist" "style": "sublist"