diff --git a/eslint.config.mjs b/eslint.config.mjs index ffe22ea3..6b83e6c7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -54,6 +54,7 @@ export default [ "multiline-comment-style": [ "error", "separate-lines" ], "no-empty-function": "off", "no-implicit-coercion": "off", + "no-inline-comments": [ "error", { "ignorePattern": " @type \\{.+\\} " } ], "no-magic-numbers": "off", "no-param-reassign": "off", "no-plusplus": "off", diff --git a/example/typescript/type-check.ts b/example/typescript/type-check.ts index e968f4ef..48ae55da 100644 --- a/example/typescript/type-check.ts +++ b/example/typescript/type-check.ts @@ -30,7 +30,7 @@ function assertLintResults(results: LintResults) { assert.equal(results["string"][0].lineNumber, 1); 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].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].errorContext, null); assert.deepEqual(results["string"][0].errorRange, [ 9, 1 ]); diff --git a/helpers/helpers.cjs b/helpers/helpers.cjs index 37bb9065..5151e3c5 100644 --- a/helpers/helpers.cjs +++ b/helpers/helpers.cjs @@ -111,10 +111,11 @@ module.exports.cloneIfArray = cloneIfArray; /** * 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. */ function cloneIfUrl(url) { + // @ts-ignore return isUrl(url) ? new URL(url) : url; } module.exports.cloneIfUrl = cloneIfUrl; @@ -139,7 +140,7 @@ module.exports.getHtmlAttributeRe = function getHtmlAttributeRe(name) { function isBlankLine(line) { const startComment = ""; - const removeComments = (s) => { + const removeComments = (/** @type {string} */ s) => { while (true) { const start = s.indexOf(startComment); const end = s.indexOf(endComment); @@ -177,8 +178,8 @@ const startsWithPipeRe = /^ *\|/; const notCrLfRe = /[^\r\n]/g; const notSpaceCrLfRe = /[^ \r\n]/g; const trailingSpaceRe = / +[\r\n]/g; -const replaceTrailingSpace = (s) => s.replace(notCrLfRe, safeCommentCharacter); -module.exports.clearHtmlCommentText = function clearHtmlCommentText(text) { +const replaceTrailingSpace = (/** @type {string} */ s) => s.replace(notCrLfRe, safeCommentCharacter); +module.exports.clearHtmlCommentText = function clearHtmlCommentText(/** @type {string} */ text) { let i = 0; while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) { 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 -module.exports.escapeForRegExp = function escapeForRegExp(str) { +module.exports.escapeForRegExp = function escapeForRegExp(/** @type {string} */ str) { return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); }; @@ -355,7 +356,7 @@ module.exports.hasOverlap = function hasOverlap(rangeA, rangeB) { // Determines if the front matter includes a title module.exports.frontMatterHasTitle = - function frontMatterHasTitle(frontMatterLines, frontMatterTitlePattern) { + function frontMatterHasTitle(/** @type {readonly string[]} */ frontMatterLines, /** @type {string} */ frontMatterTitlePattern) { const ignoreFrontMatter = (frontMatterTitlePattern !== undefined) && !frontMatterTitlePattern; const frontMatterTitleRe = @@ -367,18 +368,31 @@ module.exports.frontMatterHasTitle = frontMatterLines.some((line) => frontMatterTitleRe.test(line)); }; +/** + * Result object for getReferenceLinkImageData. + * + * @typedef {Object} GetReferenceLinkImageDataResult + * @property {Map} references References. + * @property {Map} shortcuts Shortcuts. + * @property {Map} definitions Definitions. + * @property {[string, number][]} duplicateDefinitions Duplicate definitions. + * @property {number[]} definitionLineIndices Definition line indices. + */ + /** * Returns an object with information about reference links and images. * * @param {MicromarkToken[]} tokens Micromark tokens. - * @returns {Object} Reference link/image data. + * @returns {GetReferenceLinkImageDataResult} Reference link/image data. */ function getReferenceLinkImageData(tokens) { - const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " "); - const getText = (t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join(""); + const normalizeReference = (/** @type {string} */ s) => s.toLowerCase().trim().replace(/\s+/g, " "); + const getText = (/** @type {MicromarkToken} */ t) => t?.children.filter((c) => c.type !== "blockQuotePrefix").map((c) => c.text).join(""); + /** @type {Map} */ const references = new Map(); + /** @type {Map} */ const shortcuts = new Map(); - const addReferenceToDictionary = (token, label, isShortcut) => { + const addReferenceToDictionary = (/** @type {MicromarkToken} */ token, /** @type {string} */ label, /** @type {boolean} */ isShortcut) => { const referenceDatum = [ token.startLine - 1, token.startColumn - 1, @@ -390,8 +404,11 @@ function getReferenceLinkImageData(tokens) { referenceData.push(referenceDatum); dictionary.set(reference, referenceData); }; + /** @type {Map} */ const definitions = new Map(); + /** @type {number[]} */ const definitionLineIndices = []; + /** @type {[string, number][]} */ const duplicateDefinitions = []; const filteredTokens = micromark.filterByTypes( @@ -433,7 +450,7 @@ function getReferenceLinkImageData(tokens) { micromark.getDescendantsByType(parent, [ "definitionDestination", "definitionDestinationRaw", "definitionDestinationString" ])[0]?.text; definitions.set( 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. * * @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. */ function getPreferredLineEnding(input, os) { @@ -530,7 +547,7 @@ module.exports.getPreferredLineEnding = getPreferredLineEnding; * Expands a path with a tilde to an absolute path. * * @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). */ function expandTildePath(file, os) { @@ -591,6 +608,7 @@ function convertLintErrorsVersion2To1(errors) { * @returns {LintErrors} Lint errors (v0). */ function convertLintErrorsVersion2To0(errors) { + /** @type {Object.} */ const dictionary = {}; for (const error of errors) { const ruleName = error.ruleNames[0]; @@ -606,10 +624,11 @@ function convertLintErrorsVersion2To0(errors) { * Copies and transforms lint results from resultVersion 3 to ?. * * @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?). */ function copyAndTransformResults(results, transform) { + /** @type {Object.} */ const newResults = {}; Object.defineProperty(newResults, "toString", { "value": results.toString }); for (const key of Object.keys(results)) { diff --git a/helpers/micromark-helpers.cjs b/helpers/micromark-helpers.cjs index d3544dae..a208d684 100644 --- a/helpers/micromark-helpers.cjs +++ b/helpers/micromark-helpers.cjs @@ -17,6 +17,7 @@ const { flatTokensSymbol, htmlFlowSymbol, newLineRe } = require("./shared.cjs"); * @returns {boolean} True iff the token is within an htmlFlow type. */ function inHtmlFlow(token) { + // @ts-ignore return Boolean(token[htmlFlowSymbol]); } @@ -126,8 +127,11 @@ function filterByPredicate(tokens, allowed, transformChildren) { * @returns {Token[]} Filtered tokens. */ function filterByTypes(tokens, types, htmlFlow) { - const predicate = (token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token)); - const flatTokens = tokens[flatTokensSymbol]; + const predicate = (/** @type {Token} */ token) => types.includes(token.type) && (htmlFlow || !inHtmlFlow(token)); + /** @type {Token[]} */ + const flatTokens = + // @ts-ignore + tokens[flatTokensSymbol]; if (flatTokens) { return flatTokens.filter(predicate); } @@ -163,7 +167,7 @@ function getBlockQuotePrefixText(tokens, lineNumber, count = 1) { function getDescendantsByType(parent, typePath) { let tokens = Array.isArray(parent) ? parent : [ parent ]; 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)); } return tokens; diff --git a/lib/cache.mjs b/lib/cache.mjs index 43c44cce..cc7300bc 100644 --- a/lib/cache.mjs +++ b/lib/cache.mjs @@ -6,6 +6,7 @@ import { filterByTypes } from "../helpers/micromark-helpers.cjs"; /** @typedef {import("markdownlint").RuleParams} RuleParams */ /** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ /** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */ +/** @typedef {import("../helpers/helpers.cjs").GetReferenceLinkImageDataResult} GetReferenceLinkImageDataResult */ /** @type {Map} */ const map = new Map(); @@ -68,9 +69,10 @@ export function filterByTypesCached(types, htmlFlow) { /** * 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() { + // @ts-ignore return getCached( getReferenceLinkImageData.name, () => helpersGetReferenceLinkImageData(micromarkTokens()) diff --git a/lib/constants.mjs b/lib/constants.mjs index 71e855a4..cf75289d 100644 --- a/lib/constants.mjs +++ b/lib/constants.mjs @@ -1,5 +1,6 @@ // @ts-check +/** @type {string[]} */ export const deprecatedRuleNames = []; export const fixableRuleNames = [ "MD004", "MD005", "MD007", "MD009", "MD010", "MD011", diff --git a/lib/markdownit.cjs b/lib/markdownit.cjs index 05a0cc23..51013985 100644 --- a/lib/markdownit.cjs +++ b/lib/markdownit.cjs @@ -127,6 +127,7 @@ function annotateAndFreezeTokens(tokens, lines) { } // Annotate children with lineNumber if (token.children) { + /** @type {number[]} */ const codeSpanExtraLines = []; if (token.children.some((child) => child.type === "code_inline")) { forEachInlineCodeSpan(token.content, (code) => { @@ -140,7 +141,7 @@ function annotateAndFreezeTokens(tokens, lines) { if ((child.type === "softbreak") || (child.type === "hardbreak")) { lineNumber++; } else if (child.type === "code_inline") { - lineNumber += codeSpanExtraLines.shift(); + lineNumber += codeSpanExtraLines.shift() || 0; } } } diff --git a/lib/markdownlint.d.mts b/lib/markdownlint.d.mts index 9fe0e606..85577914 100644 --- a/lib/markdownlint.d.mts +++ b/lib/markdownlint.d.mts @@ -26,39 +26,38 @@ export function lintSync(options: Options | null): LintResults; * @param {Configuration} config Configuration object. * @param {string} file Configuration file name. * @param {ConfigurationParser[] | undefined} parsers Parsing function(s). - * @param {Object} fs File system implementation. + * @param {FsLike} fs File system implementation. * @returns {Promise} Configuration object. */ -export function extendConfigPromise(config: Configuration, file: string, parsers: ConfigurationParser[] | undefined, fs: any): Promise; +export function extendConfigPromise(config: Configuration, file: string, parsers: ConfigurationParser[] | undefined, fs: FsLike): Promise; /** * Read specified configuration file. * * @param {string} file Configuration file name. - * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing - * function(s). - * @param {Object} [fs] File system implementation. + * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing function(s). + * @param {FsLike | ReadConfigCallback} [fs] File system implementation. * @param {ReadConfigCallback} [callback] Callback (err, result) function. * @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. * * @param {string} file Configuration file name. * @param {ConfigurationParser[]} [parsers] Parsing function(s). - * @param {Object} [fs] File system implementation. + * @param {FsLike} [fs] File system implementation. * @returns {Promise} Configuration object. */ -export function readConfigPromise(file: string, parsers?: ConfigurationParser[], fs?: any): Promise; +export function readConfigPromise(file: string, parsers?: ConfigurationParser[], fs?: FsLike): Promise; /** * Read specified configuration file. * * @param {string} file Configuration file name. * @param {ConfigurationParser[]} [parsers] Parsing function(s). - * @param {Object} [fs] File system implementation. + * @param {FsLike} [fs] File system implementation. * @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. * @@ -82,6 +81,19 @@ export function applyFixes(input: string, errors: LintError[]): string; * @returns {string} SemVer 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. */ @@ -120,6 +132,27 @@ export type EnabledRulesPerLineNumberResult = { */ rulesSeverity: Map; }; +/** + * 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. */ @@ -418,7 +451,7 @@ export type Options = { /** * File system implementation. */ - fs?: any; + fs?: FsLike; /** * True to catch exceptions. */ @@ -467,15 +500,15 @@ export type LintError = { /** * Link to more information. */ - ruleInformation: string; + ruleInformation: string | null; /** * Detail about the error. */ - errorDetail: string; + errorDetail: string | null; /** * Context for the error. */ - errorContext: string; + errorContext: string | null; /** * Column number (1-based) and length. */ diff --git a/lib/markdownlint.mjs b/lib/markdownlint.mjs index e01035d1..565930a4 100644 --- a/lib/markdownlint.mjs +++ b/lib/markdownlint.mjs @@ -24,15 +24,17 @@ function validateRuleList(ruleList, synchronous) { // No need to validate if only using built-in rules return result; } + /** @type {Object.} */ const allIds = {}; for (const [ index, rule ] of ruleList.entries()) { const customIndex = index - rules.length; - // eslint-disable-next-line jsdoc/require-jsdoc - function newError(property, value) { + // eslint-disable-next-line jsdoc/reject-any-type, jsdoc/require-jsdoc + function newError(/** @type {string} */ property, /** @type {any} */ value) { return new Error( `Property '${property}' of custom rule at index ${customIndex} is incorrect: '${value}'.`); } for (const property of [ "names", "tags" ]) { + // @ts-ignore const value = rule[property]; if (!result && (!value || !Array.isArray(value) || (value.length === 0) || @@ -45,6 +47,7 @@ function validateRuleList(ruleList, synchronous) { [ "function", "function" ] ]) { const property = propertyInfo[0]; + // @ts-ignore const value = rule[property]; if (!result && (!value || (typeof value !== propertyInfo[1]))) { result = newError(property, value); @@ -113,10 +116,12 @@ function newResults(ruleList) { * * @param {boolean} useAlias True if rule alias should be used instead of name. * @returns {string} String representation of the instance. + * @this {LintResults} */ 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; + /** @type {Object. | null} */ let ruleNameToRule = null; const results = []; const keys = Object.keys(lintResults); @@ -127,6 +132,7 @@ function newResults(ruleList) { for (const result of fileResults) { const ruleMoniker = result.ruleNames ? result.ruleNames.join("/") : + // @ts-ignore (result.ruleName + "/" + result.ruleAlias); results.push( file + ": " + @@ -173,14 +179,23 @@ function newResults(ruleList) { 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). * * @param {string} content Markdown content. * @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) { + /** @type {string[]} */ let frontMatterLines = []; if (frontMatter) { const frontMatterMatch = content.match(frontMatter); @@ -207,6 +222,7 @@ function removeFrontMatter(content, frontMatter) { * @returns {Object.} Map of alias to rule name. */ function mapAliasToRuleNames(ruleList) { + /** @type {Object.} */ const aliasToRuleNames = {}; // const tagToRuleNames = {}; for (const rule of ruleList) { @@ -357,7 +373,7 @@ function getEnabledRulesPerLineNumber( const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); // Helper functions // 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()) { if (!noInlineConfig) { let match = null; @@ -378,7 +394,7 @@ function getEnabledRulesPerLineNumber( } } // eslint-disable-next-line jsdoc/require-jsdoc - function configureFile(action, parameter) { + function configureFile(/** @type {string} */ action, /** @type {string} */ parameter) { if (action === "CONFIGURE-FILE") { const { "config": parsed } = parseConfiguration( "CONFIGURE-FILE", parameter, configParsers @@ -392,7 +408,7 @@ function getEnabledRulesPerLineNumber( } } // eslint-disable-next-line jsdoc/require-jsdoc - function applyEnableDisable(action, parameter, state) { + function applyEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter, /** @type {Map} */ state) { state = new Map(state); const enabled = (action.startsWith("ENABLE")); const trimmed = parameter && parameter.trim(); @@ -406,13 +422,13 @@ function getEnabledRulesPerLineNumber( return state; } // 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")) { enabledRules = applyEnableDisable(action, parameter, enabledRules); } } // eslint-disable-next-line jsdoc/require-jsdoc - function captureRestoreEnableDisable(action, parameter) { + function captureRestoreEnableDisable(/** @type {string} */ action, /** @type {string} */ parameter) { if (action === "CAPTURE") { capturedRules = enabledRules; } else if (action === "RESTORE") { @@ -426,7 +442,7 @@ function getEnabledRulesPerLineNumber( enabledRulesPerLineNumber.push(enabledRules); } // 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 disableNextLine = (action === "DISABLE-NEXT-LINE"); if (disableLine || disableNextLine) { @@ -496,7 +512,8 @@ function lintContent( synchronous, 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) content = content.replace(/^\uFEFF/, ""); // Remove front matter @@ -531,7 +548,7 @@ function lintContent( // Parse content into lines and get markdown-it tokens const lines = content.split(helpers.newLineRe); // Function to run after fetching markdown-it tokens (when needed) - const lintContentInternal = (markdownitTokens) => { + const lintContentInternal = (/** @type {MarkdownItToken[]} */ markdownitTokens) => { // Create (frozen) parameters for rules /** @type {MarkdownParsers} */ // @ts-ignore @@ -585,15 +602,17 @@ function lintContent( ...paramsBase, ...tokens, parsers, + /** @type {RuleConfiguration} */ + // @ts-ignore "config": effectiveConfig[ruleName] }); // eslint-disable-next-line jsdoc/require-jsdoc - function throwError(property) { + function throwError(/** @type {string} */ property) { throw new Error( `Value of '${property}' passed to onError by '${ruleName}' is incorrect for '${name}'.`); } // eslint-disable-next-line jsdoc/require-jsdoc - function onError(errorInfo) { + function onError(/** @type {RuleOnErrorInfo} */ errorInfo) { if (!errorInfo || !helpers.isNumber(errorInfo.lineNumber) || (errorInfo.lineNumber < 1) || @@ -683,6 +702,7 @@ function lintContent( }); } // Call (possibly external) rule function to report errors + // @ts-ignore const catchCallsOnError = (error) => onError({ "lineNumber": 1, "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} noInlineConfig Whether to allow inline configuration. * @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 {LintContentCallback} callback Callback (err, result) function. * @returns {void} @@ -792,7 +812,7 @@ function lintFile( synchronous, callback) { // eslint-disable-next-line jsdoc/require-jsdoc - function lintContentWrapper(err, content) { + function lintContentWrapper(/** @type {Error | null} */ err, /** @type {string} */ content) { if (err) { return callback(err); } @@ -832,6 +852,8 @@ function lintInput(options, synchronous, callback) { // Normalize inputs options = options || {}; callback = callback || function noop() {}; + /** @type {Rule[]} */ + // @ts-ignore const customRuleList = [ options.customRules || [] ] .flat() @@ -851,6 +873,7 @@ function lintInput(options, synchronous, callback) { callback(ruleErr); return; } + /** @type {string[]} */ let files = []; if (Array.isArray(options.files)) { files = [ ...options.files ]; @@ -866,11 +889,14 @@ function lintInput(options, synchronous, callback) { options.frontMatter; const handleRuleFailures = !!options.handleRuleFailures; const noInlineConfig = !!options.noInlineConfig; + // @ts-ignore // eslint-disable-next-line dot-notation const resultVersion = (options["resultVersion"] === undefined) ? 3 : options["resultVersion"]; const 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."); }); + /** @type {FsLike} */ + // @ts-ignore const fs = options.fs || nodeFs; const aliasToRuleNames = mapAliasToRuleNames(ruleList); const results = newResults(ruleList); @@ -878,14 +904,16 @@ function lintInput(options, synchronous, callback) { let concurrency = 0; // eslint-disable-next-line jsdoc/require-jsdoc function lintWorker() { - let currentItem = null; + /** @type {string | undefined} */ + let currentItem = undefined; // eslint-disable-next-line jsdoc/require-jsdoc - function lintWorkerCallback(err, result) { + function lintWorkerCallback(/** @type {Error | null} */ err, /** @type {LintError[] | undefined} */ result) { concurrency--; if (err) { done = true; return callback(err); } + // @ts-ignore results[currentItem] = result; if (!synchronous) { lintWorker(); @@ -894,10 +922,9 @@ function lintInput(options, synchronous, callback) { } if (done) { // Abort for error or nothing left to do - } else if (files.length > 0) { + } else if ((currentItem = files.shift())) { // Lint next file concurrency++; - currentItem = files.shift(); lintFile( ruleList, aliasToRuleNames, @@ -1013,15 +1040,24 @@ export function lintSync(options) { 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 * using path.resolve() with require.resolve() as a fallback. * * @param {string} configFile Configuration file name. * @param {string} referenceId Referenced identifier to resolve. - * @param {Object} fs File system implementation. - * @param {ResolveConfigExtendsCallback} callback Callback (err, result) - * function. + * @param {FsLike} fs File system implementation. + * @param {ResolveConfigExtendsCallback} callback Callback (err, result) function. * @returns {void} */ function resolveConfigExtends(configFile, referenceId, fs, callback) { @@ -1049,7 +1085,7 @@ function resolveConfigExtends(configFile, referenceId, fs, callback) { * * @param {string} configFile Configuration file name. * @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. */ function resolveConfigExtendsSync(configFile, referenceId, fs) { @@ -1074,9 +1110,8 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) { * * @param {Configuration} config Configuration object. * @param {string} file Configuration file name. - * @param {ConfigurationParser[] | undefined} parsers Parsing - * function(s). - * @param {Object} fs File system implementation. + * @param {ConfigurationParser[] | undefined} parsers Parsing function(s). + * @param {FsLike} fs File system implementation. * @param {ReadConfigCallback} callback Callback (err, result) function. * @returns {void} */ @@ -1116,7 +1151,7 @@ function extendConfig(config, file, parsers, fs, callback) { * @param {Configuration} config Configuration object. * @param {string} file Configuration file name. * @param {ConfigurationParser[] | undefined} parsers Parsing function(s). - * @param {Object} fs File system implementation. + * @param {FsLike} fs File system implementation. * @returns {Promise} Configuration object. */ export function extendConfigPromise(config, file, parsers, fs) { @@ -1135,16 +1170,17 @@ export function extendConfigPromise(config, file, parsers, fs) { * Read specified configuration file. * * @param {string} file Configuration file name. - * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing - * function(s). - * @param {Object} [fs] File system implementation. + * @param {ConfigurationParser[] | ReadConfigCallback} [parsers] Parsing function(s). + * @param {FsLike | ReadConfigCallback} [fs] File system implementation. * @param {ReadConfigCallback} [callback] Callback (err, result) function. * @returns {void} */ export function readConfigAsync(file, parsers, fs, callback) { if (!callback) { if (fs) { + // @ts-ignore callback = fs; + // @ts-ignore fs = null; } else { // @ts-ignore @@ -1153,12 +1189,12 @@ export function readConfigAsync(file, parsers, fs, callback) { parsers = null; } } - if (!fs) { - fs = nodeFs; - } + /** @type {FsLike} */ + // @ts-ignore + const fsLike = fs || nodeFs; // Read file file = helpers.expandTildePath(file, os); - fs.readFile(file, "utf8", (err, content) => { + fsLike.readFile(file, "utf8", (err, content) => { if (err) { // @ts-ignore return callback(err); @@ -1172,7 +1208,7 @@ export function readConfigAsync(file, parsers, fs, callback) { } // Extend configuration // @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 {ConfigurationParser[]} [parsers] Parsing function(s). - * @param {Object} [fs] File system implementation. + * @param {FsLike} [fs] File system implementation. * @returns {Promise} Configuration object. */ export function readConfigPromise(file, parsers, fs) { @@ -1201,16 +1237,16 @@ export function readConfigPromise(file, parsers, fs) { * * @param {string} file Configuration file name. * @param {ConfigurationParser[]} [parsers] Parsing function(s). - * @param {Object} [fs] File system implementation. + * @param {FsLike} [fs] File system implementation. * @returns {Configuration} Configuration object. */ export function readConfigSync(file, parsers, fs) { - if (!fs) { - fs = nodeFs; - } + /** @type {FsLike} */ + // @ts-ignore + const fsLike = fs || nodeFs; // Read file file = helpers.expandTildePath(file, os); - const content = fs.readFileSync(file, "utf8"); + const content = fsLike.readFileSync(file, "utf8"); // Try to parse file const { config, message } = parseConfiguration(file, content, parsers); if (!config) { @@ -1224,7 +1260,7 @@ export function readConfigSync(file, parsers, fs) { const resolvedExtends = resolveConfigExtendsSync( file, helpers.expandTildePath(configExtends, os), - fs + fsLike ); return { ...readConfigSync(resolvedExtends, parsers, fs), @@ -1512,7 +1548,7 @@ export function getVersion() { * @property {Rule[] | Rule} [customRules] Custom rules. * @property {string[] | string} [files] Files to lint. * @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 {MarkdownItFactory} [markdownItFactory] Function to create a markdown-it parser. * @property {boolean} [noInlineConfig] True to ignore HTML directives. @@ -1522,7 +1558,7 @@ export function getVersion() { /** * 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 {string[]} ruleNames Rule name(s). * @property {string} ruleDescription Rule description. - * @property {string} ruleInformation Link to more information. - * @property {string} errorDetail Detail about the error. - * @property {string} errorContext Context for the error. - * @property {number[]|null} errorRange Column number (1-based) and length. - * @property {FixInfo|null} fixInfo Fix information. + * @property {string | null} ruleInformation Link to more information. + * @property {string | null} errorDetail Detail about the error. + * @property {string | null} errorContext Context for the error. + * @property {number[] | null} errorRange Column number (1-based) and length. + * @property {FixInfo | null} fixInfo Fix information. * @property {"error" | "warning"} severity Severity of the error. */ diff --git a/lib/md004.mjs b/lib/md004.mjs index c56e3ad8..aaf6bd48 100644 --- a/lib/md004.mjs +++ b/lib/md004.mjs @@ -4,21 +4,9 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs"; import { getDescendantsByType, getParentOfType } from "../helpers/micromark-helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; -const markerToStyle = { - "-": "dash", - "+": "plus", - "*": "asterisk" -}; -const styleToMarker = { - "dash": "-", - "plus": "+", - "asterisk": "*" -}; -const differentItemStyle = { - "dash": "plus", - "plus": "asterisk", - "asterisk": "dash" -}; +const markerToStyle = (/** @type {string} */ marker) => (marker === "-") ? "dash" : ((marker === "+") ? "plus" : "asterisk"); +const styleToMarker = (/** @type {string} */ style) => (style === "dash") ? "-" : ((style === "plus") ? "+" : "*"); +const differentItemStyle = (/** @type {string} */ style) => (style === "dash") ? "plus" : ((style === "plus") ? "asterisk" : "dash"); const validStyles = new Set([ "asterisk", "consistent", @@ -36,6 +24,7 @@ export default { "function": function MD004(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = validStyles.has(style) ? style : "dash"; + /** @type {("asterisk"|"dash"|"plus")[]} */ const nestingStyles = []; for (const listUnordered of filterByTypesCached([ "listUnordered" ])) { let nesting = 0; @@ -49,12 +38,12 @@ export default { } const listItemMarkers = getDescendantsByType(listUnordered, [ "listItemPrefix", "listItemMarker" ]); for (const listItemMarker of listItemMarkers) { - const itemStyle = markerToStyle[listItemMarker.text]; + const itemStyle = markerToStyle(listItemMarker.text); if (style === "sublist") { if (!nestingStyles[nesting]) { nestingStyles[nesting] = (itemStyle === nestingStyles[nesting - 1]) ? - differentItemStyle[itemStyle] : + differentItemStyle(itemStyle) : itemStyle; } expectedStyle = nestingStyles[nesting]; @@ -74,7 +63,7 @@ export default { { "editColumn": column, "deleteCount": length, - "insertText": styleToMarker[expectedStyle] + "insertText": styleToMarker(expectedStyle) } ); } diff --git a/lib/md010.mjs b/lib/md010.mjs index 36ad1017..da3fb6d1 100644 --- a/lib/md010.mjs +++ b/lib/md010.mjs @@ -17,7 +17,7 @@ export default { const includeCode = (codeBlocks === undefined) ? true : !!codeBlocks; const ignoreCodeLanguages = new Set( (params.config.ignore_code_languages || []) - .map((language) => language.toLowerCase()) + .map((/** @type {void} */ language) => String(language).toLowerCase()) ); const spacesPerTab = params.config.spaces_per_tab; const spaceMultiplier = (spacesPerTab === undefined) ? diff --git a/lib/md022.mjs b/lib/md022.mjs index bc82429b..3e0c0560 100644 --- a/lib/md022.mjs +++ b/lib/md022.mjs @@ -4,15 +4,18 @@ import { addErrorDetailIf, isBlankLine } from "../helpers/helpers.cjs"; import { getBlockQuotePrefixText, getHeadingLevel } from "../helpers/micromark-helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; +/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ + const defaultLines = 1; -const getLinesFunction = (linesParam) => { +// eslint-disable-next-line jsdoc/reject-any-type +const getLinesFunction = (/** @type {any} */ linesParam) => { if (Array.isArray(linesParam)) { const linesArray = new Array(6).fill(defaultLines); for (const [ index, value ] of [ ...linesParam.entries() ].slice(0, 6)) { linesArray[index] = value; } - return (heading) => linesArray[getHeadingLevel(heading) - 1]; + return (/** @type {MicromarkToken} */ heading) => linesArray[getHeadingLevel(heading) - 1]; } // Coerce linesParam to a number const lines = (linesParam === undefined) ? defaultLines : Number(linesParam); diff --git a/lib/md029.mjs b/lib/md029.mjs index b623192a..ec61a8ec 100644 --- a/lib/md029.mjs +++ b/lib/md029.mjs @@ -31,7 +31,7 @@ export default { "tags": [ "ol" ], "parser": "micromark", "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" ])) { const listItemPrefixes = getDescendantsByType(listOrdered, [ "listItemPrefix" ]); let expected = 1; @@ -48,10 +48,10 @@ export default { } } // Determine effective style - let listStyle = style; - if (listStyle === "one_or_ordered") { - listStyle = incrementing ? "ordered" : "one"; - } else if (listStyle === "zero") { + const listStyle = ((style === "one") || (style === "ordered") || (style === "zero")) ? + style : + (incrementing ? "ordered" : "one"); + if (listStyle === "zero") { expected = 0; } else if (listStyle === "one") { expected = 1; diff --git a/lib/md032.mjs b/lib/md032.mjs index dcb57ced..27ac30d3 100644 --- a/lib/md032.mjs +++ b/lib/md032.mjs @@ -4,7 +4,9 @@ import { addErrorContext, isBlankLine } from "../helpers/helpers.cjs"; import { filterByPredicate, getBlockQuotePrefixText, nonContentTokens } from "../helpers/micromark-helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; -const isList = (token) => ( +/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ + +const isList = (/** @type {MicromarkToken} */ token) => ( (token.type === "listOrdered") || (token.type === "listUnordered") ); diff --git a/lib/md033.mjs b/lib/md033.mjs index 8d64a942..17965f93 100644 --- a/lib/md033.mjs +++ b/lib/md033.mjs @@ -4,6 +4,11 @@ import { addError, nextLinesRe } from "../helpers/helpers.cjs"; import { getHtmlTagInfo, getParentOfType } from "../helpers/micromark-helpers.cjs"; 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} */ export default { "names": [ "MD033", "no-inline-html" ], @@ -11,35 +16,30 @@ export default { "tags": [ "html" ], "parser": "micromark", "function": function MD033(params, onError) { - let allowedElements = params.config.allowed_elements; - allowedElements = Array.isArray(allowedElements) ? allowedElements : []; - allowedElements = allowedElements.map((element) => element.toLowerCase()); - 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()); + const allowedElements = toLowerCaseStringArray(params.config.allowed_elements); + // If not defined, use allowedElements for backward compatibility + const tableAllowedElements = toLowerCaseStringArray(params.config.table_allowed_elements || params.config.allowed_elements); for (const token of filterByTypesCached([ "htmlText" ], true)) { const htmlTagInfo = getHtmlTagInfo(token); - const elementName = htmlTagInfo?.name.toLowerCase(); - const inTable = !!getParentOfType(token, [ "table" ]); - if ( - htmlTagInfo && - !htmlTagInfo.close && !( - (!inTable && allowedElements.includes(elementName)) || - (inTable && tableAllowedElements.includes(elementName)) - ) - ) { - const range = [ - token.startColumn, - token.text.replace(nextLinesRe, "").length - ]; - addError( - onError, - token.startLine, - "Element: " + htmlTagInfo.name, - undefined, - range - ); + if (htmlTagInfo && !htmlTagInfo.close) { + const elementName = htmlTagInfo?.name.toLowerCase(); + const inTable = !!getParentOfType(token, [ "table" ]); + if ( + (inTable || !allowedElements.includes(elementName)) && + (!inTable || !tableAllowedElements.includes(elementName)) + ) { + const range = [ + token.startColumn, + token.text.replace(nextLinesRe, "").length + ]; + addError( + onError, + token.startLine, + "Element: " + htmlTagInfo.name, + undefined, + range + ); + } } } } diff --git a/lib/md034.mjs b/lib/md034.mjs index a33e4978..c624dc67 100644 --- a/lib/md034.mjs +++ b/lib/md034.mjs @@ -3,6 +3,7 @@ import { addErrorContext } from "../helpers/helpers.cjs"; import { filterByPredicate, getHtmlTagInfo, inHtmlFlow } from "../helpers/micromark-helpers.cjs"; +/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ /** @typedef {import("micromark-extension-gfm-autolink-literal")} */ /** @type {import("markdownlint").Rule} */ @@ -12,7 +13,7 @@ export default { "tags": [ "links", "url" ], "parser": "micromark", "function": function MD034(params, onError) { - const literalAutolinks = (tokens) => ( + const literalAutolinks = (/** @type {MicromarkToken[]} */ tokens) => ( filterByPredicate( tokens, (token) => { diff --git a/lib/md036.mjs b/lib/md036.mjs index 26b8928e..2213cb8e 100644 --- a/lib/md036.mjs +++ b/lib/md036.mjs @@ -4,14 +4,16 @@ import { addErrorContext, allPunctuation } from "../helpers/helpers.cjs"; import { getDescendantsByType } from "../helpers/micromark-helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; -/** @typedef {import("markdownlint").MicromarkTokenType} TokenType */ -/** @type {TokenType[][]} */ +/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ +/** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */ + +/** @type {MicromarkTokenType[][]} */ const emphasisTypes = [ [ "emphasis", "emphasisText" ], [ "strong", "strongText" ] ]; -const isParagraphChildMeaningful = (token) => !( +const isParagraphChildMeaningful = (/** @type {MicromarkToken} */ token) => !( (token.type === "htmlText") || ((token.type === "data") && (token.text.trim().length === 0)) ); diff --git a/lib/md042.mjs b/lib/md042.mjs index 2127a548..443a6720 100644 --- a/lib/md042.mjs +++ b/lib/md042.mjs @@ -4,6 +4,8 @@ import { addErrorContext } from "../helpers/helpers.cjs"; import { getDescendantsByType } from "../helpers/micromark-helpers.cjs"; import { getReferenceLinkImageData, filterByTypesCached } from "./cache.mjs"; +/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ + /** @type {import("markdownlint").Rule} */ export default { "names": [ "MD042", "no-empty-links" ], @@ -12,9 +14,9 @@ export default { "parser": "micromark", "function": function MD042(params, onError) { const { definitions } = getReferenceLinkImageData(); - const isReferenceDefinitionHash = (token) => { + const isReferenceDefinitionHash = (/** @type {MicromarkToken} */ token) => { const definition = definitions.get(token.text.trim()); - return (definition && (definition[1] === "#")); + return Boolean(definition && (definition[1] === "#")); }; const links = filterByTypesCached([ "link" ]); for (const link of links) { diff --git a/lib/md043.mjs b/lib/md043.mjs index 907a0651..5a234485 100644 --- a/lib/md043.mjs +++ b/lib/md043.mjs @@ -21,8 +21,8 @@ export default { let matchAny = false; let hasError = false; let anyHeadings = false; - const getExpected = () => requiredHeadings[i++] || "[None]"; - const handleCase = (str) => (matchCase ? str : str.toLowerCase()); + const getExpected = () => String(requiredHeadings[i++] || "[None]"); + const handleCase = (/** @type {string} */ str) => (matchCase ? str : str.toLowerCase()); for (const heading of filterByTypesCached([ "atxHeading", "setextHeading" ])) { if (!hasError) { const headingText = getHeadingText(heading); diff --git a/lib/md044.mjs b/lib/md044.mjs index 8e586b3f..0e7d6339 100644 --- a/lib/md044.mjs +++ b/lib/md044.mjs @@ -17,7 +17,7 @@ export default { "function": function MD044(params, onError) { let names = params.config.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) { // Nothing to check; avoid doing any work return; diff --git a/lib/md046.mjs b/lib/md046.mjs index f30abb97..23cba2c1 100644 --- a/lib/md046.mjs +++ b/lib/md046.mjs @@ -3,10 +3,9 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; -const tokenTypeToStyle = { - "codeFenced": "fenced", - "codeIndented": "indented" -}; +/** @typedef {import("markdownlint").MicromarkTokenType} MicromarkTokenType */ + +const tokenTypeToStyle = (/** @type {MicromarkTokenType} */ tokenType) => (tokenType === "codeFenced") ? "fenced" : "indented"; /** @type {import("markdownlint").Rule} */ export default { @@ -19,13 +18,14 @@ export default { for (const token of filterByTypesCached([ "codeFenced", "codeIndented" ])) { const { startLine, type } = token; if (expectedStyle === "consistent") { - expectedStyle = tokenTypeToStyle[type]; + expectedStyle = tokenTypeToStyle(type); } addErrorDetailIf( onError, startLine, expectedStyle, - tokenTypeToStyle[type]); + tokenTypeToStyle(type) + ); } } }; diff --git a/lib/md053.mjs b/lib/md053.mjs index 4ee3f82d..c3a9f29e 100644 --- a/lib/md053.mjs +++ b/lib/md053.mjs @@ -16,7 +16,7 @@ export default { const lines = params.lines; const { references, shortcuts, definitions, duplicateDefinitions } = getReferenceLinkImageData(); - const singleLineDefinition = (line) => ( + const singleLineDefinition = (/** @type {string} */ line) => ( line.replace(linkReferenceDefinitionRe, "").trim().length > 0 ); const deleteFixInfo = { diff --git a/lib/md054.mjs b/lib/md054.mjs index 37f88193..9ee0679b 100644 --- a/lib/md054.mjs +++ b/lib/md054.mjs @@ -5,9 +5,9 @@ import { getDescendantsByType } from "../helpers/micromark-helpers.cjs"; import { getReferenceLinkImageData, filterByTypesCached } from "./cache.mjs"; const backslashEscapeRe = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g; -const removeBackslashEscapes = (text) => text.replace(backslashEscapeRe, "$1"); +const removeBackslashEscapes = (/** @type {string} **/ text) => text.replace(backslashEscapeRe, "$1"); const autolinkDisallowedRe = /[ <>]/; -const autolinkAble = (destination) => { +const autolinkAble = (/** @type {string} */ destination) => { try { // eslint-disable-next-line no-new new URL(destination); @@ -73,9 +73,11 @@ export default { const referenceString = getDescendantsByType(link, [ "reference", "referenceString" ])[0]?.text; const isCollapsed = (referenceString === undefined); const definition = definitions.get(referenceString || label); - destination = definition && definition[1]; - isError = destination && - (isShortcut ? !shortcut : (isCollapsed ? !collapsed : !full)); + destination = (definition && definition[1]) || ""; + isError = Boolean( + destination && + (isShortcut ? !shortcut : (isCollapsed ? !collapsed : !full)) + ); } } if (isError) { diff --git a/lib/md055.mjs b/lib/md055.mjs index ed6c26b5..aed9c758 100644 --- a/lib/md055.mjs +++ b/lib/md055.mjs @@ -3,15 +3,16 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs"; import { filterByTypesCached } from "./cache.mjs"; +/** @typedef {import("markdownlint").MicromarkToken} MicromarkToken */ +/** @typedef {import("micromark-extension-gfm-table")} */ + const whitespaceTypes = new Set([ "linePrefix", "whitespace" ]); -const ignoreWhitespace = (tokens) => tokens.filter( +const ignoreWhitespace = (/** @type {MicromarkToken[]} */ tokens) => tokens.filter( (token) => !whitespaceTypes.has(token.type) ); -const firstOrNothing = (items) => items[0]; -const lastOrNothing = (items) => items[items.length - 1]; -const makeRange = (start, end) => [ start, end - start + 1 ]; - -/** @typedef {import("micromark-extension-gfm-table")} */ +const firstOrNothing = (/** @type {MicromarkToken[]} */ items) => items[0]; +const lastOrNothing = (/** @type {MicromarkToken[]} */ items) => items[items.length - 1]; +const makeRange = (/** @type {number} */ start, /** @type {number} */ end) => [ start, end - start + 1 ]; /** @type {import("markdownlint").Rule} */ export default { diff --git a/lib/md056.mjs b/lib/md056.mjs index c1da47c1..9a4ce897 100644 --- a/lib/md056.mjs +++ b/lib/md056.mjs @@ -4,7 +4,7 @@ import { addErrorDetailIf } from "../helpers/helpers.cjs"; import { getParentOfType } from "../helpers/micromark-helpers.cjs"; 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")} */ diff --git a/lib/md060.mjs b/lib/md060.mjs index 43fee807..f6b95785 100644 --- a/lib/md060.mjs +++ b/lib/md060.mjs @@ -41,6 +41,7 @@ export default { const headingRow = rows[0]; // Determine errors for style "aligned" + /** @type {RuleOnErrorInfo[]} */ const errorsIfAligned = []; if (styleAlignedAllowed) { const headingDividerColumns = filterByTypes(headingRow.children, [ "tableCellDivider" ]).map((divider) => divider.startColumn); @@ -56,7 +57,9 @@ export default { } // Determine errors for styles "compact" and "tight" + /** @type {RuleOnErrorInfo[]} */ const errorsIfCompact = []; + /** @type {RuleOnErrorInfo[]} */ const errorsIfTight = []; if ( (styleCompactAllowed || styleTightAllowed) && diff --git a/lib/micromark-parse.mjs b/lib/micromark-parse.mjs index 9cdc5f1c..14a4d0d6 100644 --- a/lib/micromark-parse.mjs +++ b/lib/micromark-parse.mjs @@ -212,7 +212,9 @@ function parseInternal( const events = getEvents(markdown, micromarkParseOptions); // Create Token objects + /** @type {MicromarkToken[]} */ const document = []; + /** @type {MicromarkToken[]} */ let flatTokens = []; /** @type {MicromarkToken} */ const root = { @@ -282,6 +284,7 @@ function parseInternal( ); current.children = tokens; // Avoid stack overflow of Array.push(...spread) + // @ts-ignore // eslint-disable-next-line unicorn/prefer-spread flatTokens = flatTokens.concat(tokens[flatTokensSymbol]); } diff --git a/lib/node-imports-browser.mjs b/lib/node-imports-browser.mjs index 33dfd78e..6c1aa91c 100644 --- a/lib/node-imports-browser.mjs +++ b/lib/node-imports-browser.mjs @@ -6,8 +6,10 @@ const throwForSync = () => { }; export const fs = { + // @ts-ignore "access": (path, callback) => callback(getError()), "accessSync": throwForSync, + // @ts-ignore "readFile": (path, options, callback) => callback(getError()), "readFileSync": throwForSync }; diff --git a/lib/parse-configuration.mjs b/lib/parse-configuration.mjs index a9e78c81..b7bd85bc 100644 --- a/lib/parse-configuration.mjs +++ b/lib/parse-configuration.mjs @@ -4,7 +4,7 @@ * Result of a call to parseConfiguration. * * @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. */ @@ -13,7 +13,7 @@ * * @param {string} name Name of the configuration file. * @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. */ 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 : {}; // Succeeded return false; - } catch(error) { - errors.push(`Parser ${index++}: ${error.message}`); + // eslint-disable-next-line jsdoc/reject-any-type + } catch(/** @type {any} */ error) { + errors.push(`Parser ${index++}: ${error?.message}`); } // Failed, try the next parser return true; diff --git a/test/snapshots/markdownlint-test-scenarios.mjs.md b/test/snapshots/markdownlint-test-scenarios.mjs.md index 08d9ffdb..b2ac0def 100644 --- a/test/snapshots/markdownlint-test-scenarios.mjs.md +++ b/test/snapshots/markdownlint-test-scenarios.mjs.md @@ -60691,6 +60691,48 @@ Generated by [AVA](https://avajs.dev). ], 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␊ ␊ @@ -60751,6 +60793,23 @@ Generated by [AVA](https://avajs.dev). - item␊ - item {MD004}␊ ␊ + - item␊ + * item␊ + + item␊ + - item␊ + * item␊ + - item␊ + + item {MD004}␊ + ␊ + - item␊ + * item␊ + + item␊ + - item␊ + * item␊ + - item␊ + + item␊ + * item {MD004}␊ + ␊