2017-12-15 22:55:51 -08:00
// @ts-check
2024-12-08 21:04:32 -08:00
// @ts-ignore
2025-01-12 18:27:14 -08:00
import { fs as nodeFs , os , path } from "#node-imports" ;
2024-11-28 20:36:44 -08:00
import { initialize as cacheInitialize } from "./cache.mjs" ;
import { version } from "./constants.mjs" ;
2025-01-12 18:27:14 -08:00
import { requireMarkdownItCjs } from "./defer-require.cjs" ;
import { resolveModule } from "./resolve-module.cjs" ;
2024-11-28 20:36:44 -08:00
import rules from "./rules.mjs" ;
2024-11-30 20:42:14 -08:00
import { parse as micromarkParse } from "./micromark-parse.mjs" ;
2025-02-28 20:25:44 -08:00
import parseConfiguration from "./parse-configuration.mjs" ;
2024-11-28 20:36:44 -08:00
import * as helpers from "../helpers/helpers.cjs" ;
2021-02-11 22:16:07 -08:00
2020-01-23 19:42:46 -08:00
/ * *
* Validate the list of rules for structure and reuse .
*
* @ param { Rule [ ] } ruleList List of rules .
2021-12-11 21:44:25 -08:00
* @ param { boolean } synchronous Whether to execute synchronously .
2022-06-11 22:40:45 -07:00
* @ returns { Error | null } Error message if validation fails .
2020-01-23 19:42:46 -08:00
* /
2021-12-11 21:44:25 -08:00
function validateRuleList ( ruleList , synchronous ) {
2018-04-27 22:05:34 -07:00
let result = null ;
2018-02-25 16:04:13 -08:00
if ( ruleList . length === rules . length ) {
// No need to validate if only using built-in rules
return result ;
}
2018-04-27 22:05:34 -07:00
const allIds = { } ;
2022-06-08 22:10:27 -07:00
for ( const [ index , rule ] of ruleList . entries ( ) ) {
2018-04-27 22:05:34 -07:00
const customIndex = index - rules . length ;
2024-04-20 21:23:06 -07:00
// eslint-disable-next-line jsdoc/require-jsdoc
2024-03-09 16:17:50 -08:00
function newError ( property , value ) {
2018-02-25 16:04:13 -08:00
return new Error (
2024-03-09 16:17:50 -08:00
` Property ' ${ property } ' of custom rule at index ${ customIndex } is incorrect: ' ${ value } '. ` ) ;
2018-02-25 16:04:13 -08:00
}
2022-06-08 22:10:27 -07:00
for ( const property of [ "names" , "tags" ] ) {
2018-04-27 22:05:34 -07:00
const value = rule [ property ] ;
2018-02-25 16:04:13 -08:00
if ( ! result &&
( ! value || ! Array . isArray ( value ) || ( value . length === 0 ) ||
2019-04-13 11:18:57 -07:00
! value . every ( helpers . isString ) || value . some ( helpers . isEmptyString ) ) ) {
2024-03-09 16:17:50 -08:00
result = newError ( property , value ) ;
2018-02-25 16:04:13 -08:00
}
2022-06-08 22:10:27 -07:00
}
for ( const propertyInfo of [
2018-02-25 16:04:13 -08:00
[ "description" , "string" ] ,
[ "function" , "function" ]
2022-06-08 22:10:27 -07:00
] ) {
2018-04-27 22:05:34 -07:00
const property = propertyInfo [ 0 ] ;
const value = rule [ property ] ;
2018-02-25 16:04:13 -08:00
if ( ! result && ( ! value || ( typeof value !== propertyInfo [ 1 ] ) ) ) {
2024-03-09 16:17:50 -08:00
result = newError ( property , value ) ;
2018-02-25 16:04:13 -08:00
}
2022-06-08 22:10:27 -07:00
}
2024-03-09 16:17:50 -08:00
if (
! result &&
( rule . parser !== undefined ) &&
( rule . parser !== "markdownit" ) &&
2024-10-01 22:41:10 -07:00
( rule . parser !== "micromark" ) &&
2024-03-09 16:17:50 -08:00
( rule . parser !== "none" )
) {
result = newError ( "parser" , rule . parser ) ;
}
2021-02-06 19:55:22 -08:00
if (
! result &&
rule . information &&
2023-07-11 21:44:45 -07:00
! helpers . isUrl ( rule . information )
2021-02-06 19:55:22 -08:00
) {
2024-03-09 16:17:50 -08:00
result = newError ( "information" , rule . information ) ;
2019-01-15 21:56:38 -08:00
}
2021-12-11 21:44:25 -08:00
if (
! result &&
( rule . asynchronous !== undefined ) &&
( typeof rule . asynchronous !== "boolean" )
) {
2024-03-09 16:17:50 -08:00
result = newError ( "asynchronous" , rule . asynchronous ) ;
2021-12-11 21:44:25 -08:00
}
if ( ! result && rule . asynchronous && synchronous ) {
result = new Error (
"Custom rule " + rule . names . join ( "/" ) + " at index " + customIndex +
" is asynchronous and can not be used in a synchronous context."
) ;
}
2018-02-25 16:04:13 -08:00
if ( ! result ) {
2022-06-08 22:10:27 -07:00
for ( const name of rule . names ) {
2018-04-27 22:05:34 -07:00
const nameUpper = name . toUpperCase ( ) ;
2018-02-25 16:04:13 -08:00
if ( ! result && ( allIds [ nameUpper ] !== undefined ) ) {
result = new Error ( "Name '" + name + "' of custom rule at index " +
customIndex + " is already used as a name or tag." ) ;
}
allIds [ nameUpper ] = true ;
2022-06-08 22:10:27 -07:00
}
for ( const tag of rule . tags ) {
2018-04-27 22:05:34 -07:00
const tagUpper = tag . toUpperCase ( ) ;
2018-02-25 16:04:13 -08:00
if ( ! result && allIds [ tagUpper ] ) {
result = new Error ( "Tag '" + tag + "' of custom rule at index " +
customIndex + " is already used as a name." ) ;
}
allIds [ tagUpper ] = false ;
2022-06-08 22:10:27 -07:00
}
2018-02-25 16:04:13 -08:00
}
2022-06-08 22:10:27 -07:00
}
2018-02-25 16:04:13 -08:00
return result ;
}
2020-01-23 19:42:46 -08:00
/ * *
* Creates a LintResults instance with toString for pretty display .
*
* @ param { Rule [ ] } ruleList List of rules .
* @ returns { LintResults } New LintResults instance .
* /
2018-02-25 16:04:13 -08:00
function newResults ( ruleList ) {
2020-09-07 20:05:36 -07:00
const lintResults = { } ;
2020-01-23 19:42:46 -08:00
// eslint-disable-next-line jsdoc/require-jsdoc
2020-09-07 20:05:36 -07:00
function toString ( useAlias ) {
2018-04-27 22:05:34 -07:00
let ruleNameToRule = null ;
const results = [ ] ;
2020-09-12 12:01:20 -07:00
const keys = Object . keys ( lintResults ) ;
keys . sort ( ) ;
2022-06-08 22:10:27 -07:00
for ( const file of keys ) {
2020-09-07 20:05:36 -07:00
const fileResults = lintResults [ file ] ;
2018-02-15 21:35:58 -08:00
if ( Array . isArray ( fileResults ) ) {
2022-06-08 22:10:27 -07:00
for ( const result of fileResults ) {
2018-04-27 22:05:34 -07:00
const ruleMoniker = result . ruleNames ?
2018-02-15 21:35:58 -08:00
result . ruleNames . join ( "/" ) :
( result . ruleName + "/" + result . ruleAlias ) ;
results . push (
2016-10-16 21:46:02 -07:00
file + ": " +
2018-02-15 21:35:58 -08:00
result . lineNumber + ": " +
ruleMoniker + " " +
result . ruleDescription +
( result . errorDetail ?
" [" + result . errorDetail + "]" :
"" ) +
( result . errorContext ?
" [Context: \"" + result . errorContext + "\"]" :
"" ) ) ;
2022-06-08 22:10:27 -07:00
}
2018-02-15 21:35:58 -08:00
} else {
if ( ! ruleNameToRule ) {
ruleNameToRule = { } ;
2022-06-08 22:10:27 -07:00
for ( const rule of ruleList ) {
2018-04-27 22:05:34 -07:00
const ruleName = rule . names [ 0 ] . toUpperCase ( ) ;
2018-02-15 21:35:58 -08:00
ruleNameToRule [ ruleName ] = rule ;
2022-06-08 22:10:27 -07:00
}
2018-02-15 21:35:58 -08:00
}
2022-06-08 22:10:27 -07:00
for ( const [ ruleName , ruleResults ] of Object . entries ( fileResults ) ) {
2018-04-27 22:05:34 -07:00
const rule = ruleNameToRule [ ruleName . toUpperCase ( ) ] ;
2022-06-08 22:10:27 -07:00
for ( const lineNumber of ruleResults ) {
// @ts-ignore
2018-04-27 22:05:34 -07:00
const nameIndex = Math . min ( useAlias ? 1 : 0 , rule . names . length - 1 ) ;
const result =
2018-02-15 21:35:58 -08:00
file + ": " +
lineNumber + ": " +
2022-06-08 22:10:27 -07:00
// @ts-ignore
2018-02-15 21:35:58 -08:00
rule . names [ nameIndex ] + " " +
2022-06-08 22:10:27 -07:00
// @ts-ignore
2018-02-15 21:35:58 -08:00
rule . description ;
results . push ( result ) ;
2022-06-08 22:10:27 -07:00
}
}
2018-02-15 21:35:58 -08:00
}
2022-06-08 22:10:27 -07:00
}
2018-02-15 21:35:58 -08:00
return results . join ( "\n" ) ;
2020-09-07 20:05:36 -07:00
}
Object . defineProperty ( lintResults , "toString" , { "value" : toString } ) ;
2020-01-23 19:42:46 -08:00
// @ts-ignore
2020-09-07 20:05:36 -07:00
return lintResults ;
2018-02-15 21:35:58 -08:00
}
2015-03-13 09:13:07 -07:00
2020-01-23 19:42:46 -08:00
/ * *
* Remove front matter ( if present at beginning of content ) .
*
* @ param { string } content Markdown content .
2023-01-29 21:13:17 -08:00
* @ param { RegExp | null } frontMatter Regular expression to match front matter .
2020-01-23 19:42:46 -08:00
* @ returns { Object } Trimmed content and front matter lines .
* /
2018-02-05 21:26:07 -08:00
function removeFrontMatter ( content , frontMatter ) {
2018-04-27 22:05:34 -07:00
let frontMatterLines = [ ] ;
2015-07-25 22:18:30 -07:00
if ( frontMatter ) {
2018-04-27 22:05:34 -07:00
const frontMatterMatch = content . match ( frontMatter ) ;
2015-07-25 22:18:30 -07:00
if ( frontMatterMatch && ! frontMatterMatch . index ) {
2018-04-27 22:05:34 -07:00
const contentMatched = frontMatterMatch [ 0 ] ;
2015-07-25 22:18:30 -07:00
content = content . slice ( contentMatched . length ) ;
2019-04-13 11:18:57 -07:00
frontMatterLines = contentMatched . split ( helpers . newLineRe ) ;
2020-09-06 20:34:10 -07:00
if ( ( frontMatterLines . length > 0 ) &&
2017-05-06 15:25:14 -07:00
( frontMatterLines [ frontMatterLines . length - 1 ] === "" ) ) {
frontMatterLines . length -- ;
}
2015-07-25 22:18:30 -07:00
}
2015-07-20 14:16:52 +02:00
}
2018-02-05 21:26:07 -08:00
return {
"content" : content ,
"frontMatterLines" : frontMatterLines
} ;
}
2020-01-23 19:42:46 -08:00
/ * *
* Map rule names / tags to canonical rule name .
*
* @ param { Rule [ ] } ruleList List of rules .
* @ returns { Object . < string , string [ ] > } Map of alias to rule name .
* /
2018-02-05 21:26:07 -08:00
function mapAliasToRuleNames ( ruleList ) {
2018-04-27 22:05:34 -07:00
const aliasToRuleNames = { } ;
// const tagToRuleNames = {};
2022-06-08 22:10:27 -07:00
for ( const rule of ruleList ) {
2018-04-27 22:05:34 -07:00
const ruleName = rule . names [ 0 ] . toUpperCase ( ) ;
2018-02-05 21:26:07 -08:00
// The following is useful for updating README.md:
// console.log(
// "* **[" + ruleName + "](doc/Rules.md#" + ruleName.toLowerCase() +
2018-04-18 22:25:45 -07:00
// ")** *" + rule.names.slice(1).join("/") + "* - " + rule.description);
2022-06-08 22:10:27 -07:00
for ( const name of rule . names ) {
2018-04-27 22:05:34 -07:00
const nameUpper = name . toUpperCase ( ) ;
2018-02-05 21:26:07 -08:00
aliasToRuleNames [ nameUpper ] = [ ruleName ] ;
2022-06-08 22:10:27 -07:00
}
for ( const tag of rule . tags ) {
2018-04-27 22:05:34 -07:00
const tagUpper = tag . toUpperCase ( ) ;
const ruleNames = aliasToRuleNames [ tagUpper ] || [ ] ;
2018-02-05 21:26:07 -08:00
ruleNames . push ( ruleName ) ;
aliasToRuleNames [ tagUpper ] = ruleNames ;
// tagToRuleNames[tag] = ruleName;
2022-06-08 22:10:27 -07:00
}
}
2018-02-05 21:26:07 -08:00
// The following is useful for updating README.md:
// Object.keys(tagToRuleNames).sort().forEach(function forTag(tag) {
// console.log("* **" + tag + "** - " +
// aliasToRuleNames[tag.toUpperCase()].join(", "));
// });
2020-01-23 19:42:46 -08:00
// @ts-ignore
2018-02-05 21:26:07 -08:00
return aliasToRuleNames ;
}
2020-01-23 19:42:46 -08:00
/ * *
* Apply ( and normalize ) configuration object .
*
* @ param { Rule [ ] } ruleList List of rules .
* @ param { Configuration } config Configuration object .
* @ param { Object . < string , string [ ] > } aliasToRuleNames Map of alias to rule
* names .
* @ returns { Configuration } Effective configuration .
* /
2018-02-25 16:04:13 -08:00
function getEffectiveConfig ( ruleList , config , aliasToRuleNames ) {
2019-03-12 22:23:12 -07:00
const defaultKey = Object . keys ( config ) . filter (
( key ) => key . toUpperCase ( ) === "DEFAULT"
) ;
2018-04-27 22:05:34 -07:00
const ruleDefault = ( defaultKey . length === 0 ) || ! ! config [ defaultKey [ 0 ] ] ;
2023-11-08 19:49:02 -08:00
/** @type {Configuration} */
2018-04-27 22:05:34 -07:00
const effectiveConfig = { } ;
2022-06-08 22:10:27 -07:00
for ( const rule of ruleList ) {
2018-04-27 22:05:34 -07:00
const ruleName = rule . names [ 0 ] . toUpperCase ( ) ;
2018-02-15 21:35:58 -08:00
effectiveConfig [ ruleName ] = ruleDefault ;
2022-06-08 22:10:27 -07:00
}
2023-11-09 19:47:15 -08:00
// for (const ruleName of deprecatedRuleNames) {
// effectiveConfig[ruleName] = false;
// }
2022-06-08 22:10:27 -07:00
for ( const key of Object . keys ( config ) ) {
2018-04-27 22:05:34 -07:00
let value = config [ key ] ;
2015-04-29 18:46:52 -07:00
if ( value ) {
if ( ! ( value instanceof Object ) ) {
value = { } ;
}
} else {
value = false ;
}
2018-04-27 22:05:34 -07:00
const keyUpper = key . toUpperCase ( ) ;
2022-06-08 22:10:27 -07:00
for ( const ruleName of ( aliasToRuleNames [ keyUpper ] || [ ] ) ) {
2018-02-15 21:35:58 -08:00
effectiveConfig [ ruleName ] = value ;
2022-06-08 22:10:27 -07:00
}
}
2018-02-15 21:35:58 -08:00
return effectiveConfig ;
2018-02-05 21:26:07 -08:00
}
2025-04-20 04:57:35 +00:00
/ * *
* Result object for getEnabledRulesPerLineNumber .
*
* @ typedef { Object } EnabledRulesPerLineNumberResult
* @ property { Configuration } effectiveConfig Effective configuration .
* @ property { any [ ] } enabledRulesPerLineNumber Enabled rules per line number .
* @ property { Rule [ ] } enabledRuleList Enabled rule list .
* /
2020-01-23 19:42:46 -08:00
/ * *
* Create a mapping of enabled rules per line .
*
* @ param { Rule [ ] } ruleList List of rules .
* @ param { string [ ] } lines List of content lines .
* @ param { string [ ] } frontMatterLines List of front matter lines .
* @ param { boolean } noInlineConfig Whether to allow inline configuration .
2020-04-05 19:47:12 -07:00
* @ param { Configuration } config Configuration object .
2025-02-28 20:25:44 -08:00
* @ param { ConfigurationParser [ ] | undefined } configParsers Configuration parsers .
2020-01-23 19:42:46 -08:00
* @ param { Object . < string , string [ ] > } aliasToRuleNames Map of alias to rule
* names .
2025-04-20 04:57:35 +00:00
* @ returns { EnabledRulesPerLineNumberResult } Effective configuration and enabled rules per line number .
2020-01-23 19:42:46 -08:00
* /
2018-02-05 21:26:07 -08:00
function getEnabledRulesPerLineNumber (
2020-01-23 19:42:46 -08:00
ruleList ,
lines ,
frontMatterLines ,
noInlineConfig ,
2020-04-05 19:47:12 -07:00
config ,
2022-06-05 22:32:22 -07:00
configParsers ,
2020-01-23 19:42:46 -08:00
aliasToRuleNames ) {
2020-04-05 19:47:12 -07:00
// Shared variables
2018-04-27 22:05:34 -07:00
let enabledRules = { } ;
2020-04-05 19:47:12 -07:00
let capturedRules = { } ;
2018-04-27 22:05:34 -07:00
const allRuleNames = [ ] ;
2020-04-05 19:47:12 -07:00
const enabledRulesPerLineNumber = new Array ( 1 + frontMatterLines . length ) ;
// Helper functions
2020-01-23 19:42:46 -08:00
// eslint-disable-next-line jsdoc/require-jsdoc
2021-12-21 21:31:47 -08:00
function handleInlineConfig ( input , forEachMatch , forEachLine ) {
2022-06-08 22:10:27 -07:00
for ( const [ lineIndex , line ] of input . entries ( ) ) {
2020-04-05 19:47:12 -07:00
if ( ! noInlineConfig ) {
let match = null ;
2022-02-12 17:46:46 -08:00
while ( ( match = helpers . inlineCommentStartRe . exec ( line ) ) ) {
const action = match [ 2 ] . toUpperCase ( ) ;
const startIndex = match . index + match [ 1 ] . length ;
const endIndex = line . indexOf ( "-->" , startIndex ) ;
if ( endIndex === - 1 ) {
break ;
}
const parameter = line . slice ( startIndex , endIndex ) ;
2020-11-22 14:02:36 -08:00
forEachMatch ( action , parameter , lineIndex + 1 ) ;
2020-04-05 19:47:12 -07:00
}
2019-12-04 21:31:49 -08:00
}
2020-04-05 19:47:12 -07:00
if ( forEachLine ) {
forEachLine ( ) ;
2019-12-04 21:31:49 -08:00
}
2022-06-08 22:10:27 -07:00
}
2020-04-05 19:47:12 -07:00
}
// eslint-disable-next-line jsdoc/require-jsdoc
function configureFile ( action , parameter ) {
if ( action === "CONFIGURE-FILE" ) {
2022-06-05 22:32:22 -07:00
const { "config" : parsed } = parseConfiguration (
"CONFIGURE-FILE" , parameter , configParsers
) ;
if ( parsed ) {
2020-04-05 19:47:12 -07:00
config = {
... config ,
2022-06-05 22:32:22 -07:00
... parsed
2020-04-05 19:47:12 -07:00
} ;
2019-12-04 21:31:49 -08:00
}
2019-06-08 19:26:11 -07:00
}
2015-09-26 16:55:33 -07:00
}
2020-04-05 19:47:12 -07:00
// eslint-disable-next-line jsdoc/require-jsdoc
2020-11-22 14:02:36 -08:00
function applyEnableDisable ( action , parameter , state ) {
2021-12-21 21:31:47 -08:00
state = { ... state } ;
2020-04-05 19:47:12 -07:00
const enabled = ( action . startsWith ( "ENABLE" ) ) ;
2022-02-12 17:46:46 -08:00
const trimmed = parameter && parameter . trim ( ) ;
const items = trimmed ? trimmed . toUpperCase ( ) . split ( /\s+/ ) : allRuleNames ;
2022-06-08 22:10:27 -07:00
for ( const nameUpper of items ) {
for ( const ruleName of ( aliasToRuleNames [ nameUpper ] || [ ] ) ) {
2020-11-22 14:02:36 -08:00
state [ ruleName ] = enabled ;
2022-06-08 22:10:27 -07:00
}
}
2021-12-21 21:31:47 -08:00
return state ;
2020-04-05 19:47:12 -07:00
}
// eslint-disable-next-line jsdoc/require-jsdoc
function enableDisableFile ( action , parameter ) {
if ( ( action === "ENABLE-FILE" ) || ( action === "DISABLE-FILE" ) ) {
2021-12-21 21:31:47 -08:00
enabledRules = applyEnableDisable ( action , parameter , enabledRules ) ;
2020-04-05 19:47:12 -07:00
}
}
// eslint-disable-next-line jsdoc/require-jsdoc
function captureRestoreEnableDisable ( action , parameter ) {
if ( action === "CAPTURE" ) {
2021-12-21 21:31:47 -08:00
capturedRules = enabledRules ;
2020-04-05 19:47:12 -07:00
} else if ( action === "RESTORE" ) {
2021-12-21 21:31:47 -08:00
enabledRules = capturedRules ;
2020-04-05 19:47:12 -07:00
} else if ( ( action === "ENABLE" ) || ( action === "DISABLE" ) ) {
2021-12-21 21:31:47 -08:00
enabledRules = applyEnableDisable ( action , parameter , enabledRules ) ;
2020-04-05 19:47:12 -07:00
}
}
// eslint-disable-next-line jsdoc/require-jsdoc
function updateLineState ( ) {
2021-12-21 21:31:47 -08:00
enabledRulesPerLineNumber . push ( enabledRules ) ;
2020-11-22 14:02:36 -08:00
}
// eslint-disable-next-line jsdoc/require-jsdoc
2022-05-15 15:59:11 -07:00
function disableLineNextLine ( action , parameter , lineNumber ) {
const disableLine = ( action === "DISABLE-LINE" ) ;
const disableNextLine = ( action === "DISABLE-NEXT-LINE" ) ;
if ( disableLine || disableNextLine ) {
const nextLineNumber =
frontMatterLines . length + lineNumber + ( disableNextLine ? 1 : 0 ) ;
2021-12-19 00:41:08 -05:00
enabledRulesPerLineNumber [ nextLineNumber ] =
2021-12-21 21:31:47 -08:00
applyEnableDisable (
action ,
parameter ,
2023-07-28 14:33:03 +10:00
enabledRulesPerLineNumber [ nextLineNumber ]
2021-12-21 21:31:47 -08:00
) ;
2020-11-22 14:02:36 -08:00
}
2020-04-05 19:47:12 -07:00
}
// Handle inline comments
2021-12-21 21:31:47 -08:00
handleInlineConfig ( [ lines . join ( "\n" ) ] , configureFile ) ;
2020-04-05 19:47:12 -07:00
const effectiveConfig = getEffectiveConfig (
ruleList , config , aliasToRuleNames ) ;
2022-06-08 22:10:27 -07:00
for ( const rule of ruleList ) {
2020-04-05 19:47:12 -07:00
const ruleName = rule . names [ 0 ] . toUpperCase ( ) ;
allRuleNames . push ( ruleName ) ;
enabledRules [ ruleName ] = ! ! effectiveConfig [ ruleName ] ;
2022-06-08 22:10:27 -07:00
}
2020-04-05 19:47:12 -07:00
capturedRules = enabledRules ;
2021-12-21 21:31:47 -08:00
handleInlineConfig ( lines , enableDisableFile ) ;
handleInlineConfig ( lines , captureRestoreEnableDisable , updateLineState ) ;
2022-05-15 15:59:11 -07:00
handleInlineConfig ( lines , disableLineNextLine ) ;
2024-08-22 20:35:01 -07:00
// Create the list of rules that are used at least once
const enabledRuleList = [ ] ;
for ( const [ index , ruleName ] of allRuleNames . entries ( ) ) {
if ( enabledRulesPerLineNumber . some ( ( enabledRulesForLine ) => enabledRulesForLine [ ruleName ] ) ) {
enabledRuleList . push ( ruleList [ index ] ) ;
}
}
2020-04-05 19:47:12 -07:00
// Return results
return {
effectiveConfig ,
2024-08-22 20:35:01 -07:00
enabledRulesPerLineNumber ,
enabledRuleList
2020-04-05 19:47:12 -07:00
} ;
2018-02-05 21:26:07 -08:00
}
2020-01-23 19:42:46 -08:00
/ * *
* Lints a string containing Markdown content .
*
* @ param { Rule [ ] } ruleList List of rules .
2024-12-27 21:22:14 -08:00
* @ param { Object . < string , string [ ] > } aliasToRuleNames Map of alias to rule names .
2020-01-23 19:42:46 -08:00
* @ param { string } name Identifier for the content .
2020-11-24 16:25:08 -08:00
* @ param { string } content Markdown content .
2024-12-25 20:42:32 -08:00
* @ param { MarkdownItFactory } markdownItFactory Function to create a markdown - it parser .
2020-01-23 19:42:46 -08:00
* @ param { Configuration } config Configuration object .
2025-02-28 20:25:44 -08:00
* @ param { ConfigurationParser [ ] | undefined } configParsers Configuration parsers .
2023-01-29 21:13:17 -08:00
* @ param { RegExp | null } frontMatter Regular expression for front matter .
2020-01-23 19:42:46 -08:00
* @ 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 .
2024-12-27 21:22:14 -08:00
* @ param { boolean } synchronous Whether to execute synchronously .
2023-09-04 21:41:16 -07:00
* @ param { LintContentCallback } callback Callback ( err , result ) function .
2020-01-23 19:42:46 -08:00
* @ returns { void }
* /
2018-02-05 21:26:07 -08:00
function lintContent (
2019-01-19 12:52:13 -08:00
ruleList ,
2023-03-12 20:49:41 -07:00
aliasToRuleNames ,
2019-01-19 12:52:13 -08:00
name ,
content ,
2024-12-25 20:42:32 -08:00
markdownItFactory ,
2019-01-19 12:52:13 -08:00
config ,
2022-06-05 22:32:22 -07:00
configParsers ,
2019-01-19 12:52:13 -08:00
frontMatter ,
2019-05-18 12:32:52 -07:00
handleRuleFailures ,
2019-01-19 12:52:13 -08:00
noInlineConfig ,
resultVersion ,
2024-12-27 21:22:14 -08:00
synchronous ,
2018-02-25 16:04:13 -08:00
callback ) {
2024-12-27 21:22:14 -08:00
// Provide a consistent error-reporting callback
const callbackError = ( error ) => callback ( error instanceof Error ? error : new Error ( error ) ) ;
2018-02-05 21:26:07 -08:00
// Remove UTF-8 byte order marker (if present)
2020-09-06 20:34:10 -07:00
content = content . replace ( /^\uFEFF/ , "" ) ;
2018-02-05 21:26:07 -08:00
// Remove front matter
2018-04-27 22:05:34 -07:00
const removeFrontMatterResult = removeFrontMatter ( content , frontMatter ) ;
2022-06-04 22:59:19 -07:00
const { frontMatterLines } = removeFrontMatterResult ;
content = removeFrontMatterResult . content ;
// Get enabled rules per line (with HTML comments present)
2024-08-22 20:35:01 -07:00
const { effectiveConfig , enabledRulesPerLineNumber , enabledRuleList } =
2020-04-05 19:47:12 -07:00
getEnabledRulesPerLineNumber (
ruleList ,
2022-06-04 22:59:19 -07:00
content . split ( helpers . newLineRe ) ,
2020-04-05 19:47:12 -07:00
frontMatterLines ,
noInlineConfig ,
config ,
2022-06-05 22:32:22 -07:00
configParsers ,
2023-03-12 20:49:41 -07:00
aliasToRuleNames
2020-04-05 19:47:12 -07:00
) ;
2024-08-22 20:35:01 -07:00
const needMarkdownItTokens = enabledRuleList . some (
2024-08-21 21:19:40 -07:00
( rule ) => ( rule . parser === "markdownit" ) || ( rule . parser === undefined )
) ;
2025-04-14 03:05:07 +00:00
const needMicromarkTokens = enabledRuleList . some (
( rule ) => ( rule . parser === "micromark" )
) ;
2024-10-11 22:18:51 -07:00
const customRulesPresent = ( ruleList . length !== rules . length ) ;
2023-01-15 21:41:22 -08:00
// Parse content into parser tokens
2025-04-14 03:05:07 +00:00
const micromarkTokens = needMicromarkTokens ?
micromarkParse ( content , { "freezeTokens" : customRulesPresent } ) :
[ ] ;
2023-01-15 21:41:22 -08:00
// Hide the content of HTML comments from rules
2024-09-14 17:33:46 -07:00
const preClearedContent = content ;
2022-06-04 22:59:19 -07:00
content = helpers . clearHtmlCommentText ( content ) ;
2024-09-14 17:33:46 -07:00
// Parse content into lines and get markdown-it tokens
2022-06-04 22:59:19 -07:00
const lines = content . split ( helpers . newLineRe ) ;
2024-12-27 21:22:14 -08:00
// Function to run after fetching markdown-it tokens (when needed)
const lintContentInternal = ( markdownitTokens ) => {
// Create (frozen) parameters for rules
/** @type {MarkdownParsers} */
// @ts-ignore
const parsersMarkdownIt = Object . freeze ( {
"markdownit" : Object . freeze ( {
"tokens" : markdownitTokens
} )
} ) ;
/** @type {MarkdownParsers} */
// @ts-ignore
const parsersMicromark = Object . freeze ( {
"micromark" : Object . freeze ( {
"tokens" : micromarkTokens
} )
} ) ;
/** @type {MarkdownParsers} */
// @ts-ignore
const parsersNone = Object . freeze ( { } ) ;
const paramsBase = {
name ,
version ,
"lines" : Object . freeze ( lines ) ,
"frontMatterLines" : Object . freeze ( frontMatterLines )
2022-03-20 12:59:35 -07:00
} ;
2024-12-27 21:22:14 -08:00
cacheInitialize ( {
... paramsBase ,
"parsers" : parsersMicromark ,
"config" : null
} ) ;
// Function to run for each rule
let results = [ ] ;
/ * *
* @ param { Rule } rule Rule .
* @ returns { Promise < void > | null } Promise .
* /
const forRule = ( rule ) => {
// Configure rule
const ruleName = rule . names [ 0 ] . toUpperCase ( ) ;
const tokens = { } ;
let parsers = parsersNone ;
if ( rule . parser === undefined ) {
tokens . tokens = markdownitTokens ;
parsers = parsersMarkdownIt ;
} else if ( rule . parser === "markdownit" ) {
parsers = parsersMarkdownIt ;
} else if ( rule . parser === "micromark" ) {
parsers = parsersMicromark ;
2023-07-11 21:44:45 -07:00
}
2024-12-27 21:22:14 -08:00
const params = {
... paramsBase ,
... tokens ,
parsers ,
"config" : effectiveConfig [ ruleName ]
} ;
// eslint-disable-next-line jsdoc/require-jsdoc
function throwError ( property ) {
throw new Error (
` Value of ' ${ property } ' passed to onError by ' ${ ruleName } ' is incorrect for ' ${ name } '. ` ) ;
2018-02-27 21:14:02 -08:00
}
2024-12-27 21:22:14 -08:00
// eslint-disable-next-line jsdoc/require-jsdoc
function onError ( errorInfo ) {
if ( ! errorInfo ||
! helpers . isNumber ( errorInfo . lineNumber ) ||
( errorInfo . lineNumber < 1 ) ||
( errorInfo . lineNumber > lines . length ) ) {
throwError ( "lineNumber" ) ;
2019-09-14 13:39:27 -07:00
}
2024-12-27 21:22:14 -08:00
const lineNumber = errorInfo . lineNumber + frontMatterLines . length ;
if ( ! enabledRulesPerLineNumber [ lineNumber ] [ ruleName ] ) {
return ;
2019-09-14 13:39:27 -07:00
}
2024-12-27 21:22:14 -08:00
if ( errorInfo . detail &&
! helpers . isString ( errorInfo . detail ) ) {
throwError ( "detail" ) ;
2019-09-14 13:39:27 -07:00
}
2024-12-27 21:22:14 -08:00
if ( errorInfo . context &&
! helpers . isString ( errorInfo . context ) ) {
throwError ( "context" ) ;
}
if ( errorInfo . information &&
! helpers . isUrl ( errorInfo . information ) ) {
throwError ( "information" ) ;
2019-09-14 13:39:27 -07:00
}
2024-12-27 21:22:14 -08:00
if ( errorInfo . range &&
( ! Array . isArray ( errorInfo . range ) ||
( errorInfo . range . length !== 2 ) ||
! helpers . isNumber ( errorInfo . range [ 0 ] ) ||
( errorInfo . range [ 0 ] < 1 ) ||
! helpers . isNumber ( errorInfo . range [ 1 ] ) ||
( errorInfo . range [ 1 ] < 1 ) ||
( ( errorInfo . range [ 0 ] + errorInfo . range [ 1 ] - 1 ) >
lines [ errorInfo . lineNumber - 1 ] . length ) ) ) {
throwError ( "range" ) ;
}
const fixInfo = errorInfo . fixInfo ;
const cleanFixInfo = { } ;
if ( fixInfo ) {
if ( ! helpers . isObject ( fixInfo ) ) {
throwError ( "fixInfo" ) ;
}
if ( fixInfo . lineNumber !== undefined ) {
if ( ( ! helpers . isNumber ( fixInfo . lineNumber ) ||
( fixInfo . lineNumber < 1 ) ||
( fixInfo . lineNumber > lines . length ) ) ) {
throwError ( "fixInfo.lineNumber" ) ;
}
cleanFixInfo . lineNumber =
fixInfo . lineNumber + frontMatterLines . length ;
}
const effectiveLineNumber = fixInfo . lineNumber || errorInfo . lineNumber ;
if ( fixInfo . editColumn !== undefined ) {
if ( ( ! helpers . isNumber ( fixInfo . editColumn ) ||
( fixInfo . editColumn < 1 ) ||
( fixInfo . editColumn >
lines [ effectiveLineNumber - 1 ] . length + 1 ) ) ) {
throwError ( "fixInfo.editColumn" ) ;
}
cleanFixInfo . editColumn = fixInfo . editColumn ;
}
if ( fixInfo . deleteCount !== undefined ) {
if ( ( ! helpers . isNumber ( fixInfo . deleteCount ) ||
( fixInfo . deleteCount < - 1 ) ||
( fixInfo . deleteCount >
lines [ effectiveLineNumber - 1 ] . length ) ) ) {
throwError ( "fixInfo.deleteCount" ) ;
}
cleanFixInfo . deleteCount = fixInfo . deleteCount ;
}
if ( fixInfo . insertText !== undefined ) {
if ( ! helpers . isString ( fixInfo . insertText ) ) {
throwError ( "fixInfo.insertText" ) ;
}
cleanFixInfo . insertText = fixInfo . insertText ;
2019-10-19 17:34:02 -07:00
}
2019-09-14 13:39:27 -07:00
}
2024-12-27 21:22:14 -08:00
const information = errorInfo . information || rule . information ;
results . push ( {
lineNumber ,
"ruleName" : rule . names [ 0 ] ,
"ruleNames" : rule . names ,
"ruleDescription" : rule . description ,
"ruleInformation" : information ? information . href : null ,
"errorDetail" : errorInfo . detail || null ,
"errorContext" : errorInfo . context || null ,
"errorRange" : errorInfo . range ? [ ... errorInfo . range ] : null ,
"fixInfo" : fixInfo ? cleanFixInfo : null
} ) ;
2019-09-14 13:39:27 -07:00
}
2024-12-27 21:22:14 -08:00
// Call (possibly external) rule function to report errors
const catchCallsOnError = ( error ) => onError ( {
"lineNumber" : 1 ,
"detail" : ` This rule threw an exception: ${ error . message || error } `
2016-10-16 21:46:02 -07:00
} ) ;
2024-12-27 21:22:14 -08:00
const invokeRuleFunction = ( ) => rule . function ( params , onError ) ;
if ( rule . asynchronous ) {
// Asynchronous rule, ensure it returns a Promise
const ruleFunctionPromise =
Promise . resolve ( ) . then ( invokeRuleFunction ) ;
return handleRuleFailures ?
ruleFunctionPromise . catch ( catchCallsOnError ) :
ruleFunctionPromise ;
2021-12-11 21:44:25 -08:00
}
2024-12-27 21:22:14 -08:00
// Synchronous rule
try {
invokeRuleFunction ( ) ;
} catch ( error ) {
if ( handleRuleFailures ) {
catchCallsOnError ( error ) ;
} else {
throw error ;
}
2021-12-11 21:44:25 -08:00
}
2024-12-27 21:22:14 -08:00
return null ;
} ;
const formatResults = ( ) => {
// Sort results by rule name by line number
results . sort ( ( a , b ) => (
a . ruleName . localeCompare ( b . ruleName ) ||
a . lineNumber - b . lineNumber
) ) ;
if ( resultVersion < 3 ) {
// Remove fixInfo and multiple errors for the same rule and line number
const noPrevious = {
"ruleName" : null ,
"lineNumber" : - 1
} ;
results = results . filter ( ( error , index , array ) => {
delete error . fixInfo ;
const previous = array [ index - 1 ] || noPrevious ;
return (
( error . ruleName !== previous . ruleName ) ||
( error . lineNumber !== previous . lineNumber )
) ;
} ) ;
2019-05-18 12:32:52 -07:00
}
2024-12-27 21:22:14 -08:00
if ( resultVersion === 0 ) {
// Return a dictionary of rule->[line numbers]
const dictionary = { } ;
for ( const error of results ) {
const ruleLines = dictionary [ error . ruleName ] || [ ] ;
ruleLines . push ( error . lineNumber ) ;
dictionary [ error . ruleName ] = ruleLines ;
}
// @ts-ignore
results = dictionary ;
} else if ( resultVersion === 1 ) {
// Use ruleAlias instead of ruleNames
for ( const error of results ) {
error . ruleAlias = error . ruleNames [ 1 ] || error . ruleName ;
delete error . ruleNames ;
}
} else {
// resultVersion 2 or 3: Remove unwanted ruleName
for ( const error of results ) {
delete error . ruleName ;
}
2021-12-11 21:44:25 -08:00
}
2024-12-27 21:22:14 -08:00
return results ;
} ;
// Run all rules
const ruleListAsync = enabledRuleList . filter ( ( rule ) => rule . asynchronous ) ;
const ruleListSync = enabledRuleList . filter ( ( rule ) => ! rule . asynchronous ) ;
const ruleListAsyncFirst = [
... ruleListAsync ,
... ruleListSync
] ;
const callbackSuccess = ( ) => callback ( null , formatResults ( ) ) ;
try {
const ruleResults = ruleListAsyncFirst . map ( forRule ) ;
if ( ruleListAsync . length > 0 ) {
Promise . all ( ruleResults . slice ( 0 , ruleListAsync . length ) )
. then ( callbackSuccess )
. catch ( callbackError ) ;
} else {
callbackSuccess ( ) ;
}
} catch ( error ) {
callbackError ( error ) ;
} finally {
cacheInitialize ( ) ;
2019-05-18 12:32:52 -07:00
}
2024-12-27 21:22:14 -08:00
} ;
if ( ! needMarkdownItTokens || synchronous ) {
// Need/able to call into markdown-it and lintContentInternal synchronously
const markdownItTokens = needMarkdownItTokens ?
requireMarkdownItCjs ( ) . getMarkdownItTokens ( markdownItFactory ( ) , preClearedContent , lines ) :
[ ] ;
lintContentInternal ( markdownItTokens ) ;
} else {
// Need to call into markdown-it and lintContentInternal asynchronously
Promise . all ( [
// eslint-disable-next-line no-inline-comments
import ( /* webpackMode: "eager" */ "./markdownit.cjs" ) ,
// eslint-disable-next-line no-promise-executor-return
new Promise ( ( resolve ) => resolve ( markdownItFactory ( ) ) )
] ) . then ( ( [ markdownitCjs , markdownIt ] ) => {
const markdownItTokens = markdownitCjs . getMarkdownItTokens ( markdownIt , preClearedContent , lines ) ;
lintContentInternal ( markdownItTokens ) ;
} ) . catch ( callbackError ) ;
2021-12-04 17:02:11 -08:00
}
2015-04-29 18:46:52 -07:00
}
2020-01-23 19:42:46 -08:00
/ * *
* Lints a file containing Markdown content .
*
* @ param { Rule [ ] } ruleList List of rules .
2024-12-25 20:42:32 -08:00
* @ param { Object . < string , string [ ] > } aliasToRuleNames Map of alias to rule names .
2020-01-23 19:42:46 -08:00
* @ param { string } file Path of file to lint .
2024-12-25 20:42:32 -08:00
* @ param { MarkdownItFactory } markdownItFactory Function to create a markdown - it parser .
2020-01-23 19:42:46 -08:00
* @ param { Configuration } config Configuration object .
2025-02-28 20:25:44 -08:00
* @ param { ConfigurationParser [ ] | undefined } configParsers Configuration parsers .
2023-01-29 21:13:17 -08:00
* @ param { RegExp | null } frontMatter Regular expression for front matter .
2020-01-23 19:42:46 -08:00
* @ 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 .
2021-08-12 19:38:03 -07:00
* @ param { Object } fs File system implementation .
2020-01-23 19:42:46 -08:00
* @ param { boolean } synchronous Whether to execute synchronously .
2023-09-04 21:41:16 -07:00
* @ param { LintContentCallback } callback Callback ( err , result ) function .
2020-01-23 19:42:46 -08:00
* @ returns { void }
* /
2016-10-16 21:46:02 -07:00
function lintFile (
2018-02-25 16:04:13 -08:00
ruleList ,
2023-03-12 20:49:41 -07:00
aliasToRuleNames ,
2016-10-16 21:46:02 -07:00
file ,
2024-12-25 20:42:32 -08:00
markdownItFactory ,
2016-10-16 21:46:02 -07:00
config ,
2022-06-05 22:32:22 -07:00
configParsers ,
2016-10-16 21:46:02 -07:00
frontMatter ,
2019-05-18 12:32:52 -07:00
handleRuleFailures ,
2017-05-21 22:58:10 -07:00
noInlineConfig ,
2016-10-16 21:46:02 -07:00
resultVersion ,
2021-08-12 19:38:03 -07:00
fs ,
2016-10-16 21:46:02 -07:00
synchronous ,
callback ) {
2020-01-23 19:42:46 -08:00
// eslint-disable-next-line jsdoc/require-jsdoc
2015-04-29 18:46:52 -07:00
function lintContentWrapper ( err , content ) {
if ( err ) {
return callback ( err ) ;
}
2022-06-05 22:32:22 -07:00
return lintContent (
ruleList ,
2023-03-12 20:49:41 -07:00
aliasToRuleNames ,
2022-06-05 22:32:22 -07:00
file ,
content ,
2024-12-25 20:42:32 -08:00
markdownItFactory ,
2022-06-05 22:32:22 -07:00
config ,
configParsers ,
frontMatter ,
handleRuleFailures ,
noInlineConfig ,
resultVersion ,
2024-12-27 21:22:14 -08:00
synchronous ,
2022-06-05 22:32:22 -07:00
callback
) ;
2015-03-20 00:09:55 -07:00
}
// Make a/synchronous call to read file
if ( synchronous ) {
2021-08-05 22:01:29 -07:00
lintContentWrapper ( null , fs . readFileSync ( file , "utf8" ) ) ;
2015-03-20 00:09:55 -07:00
} else {
2021-08-05 22:01:29 -07:00
fs . readFile ( file , "utf8" , lintContentWrapper ) ;
2015-03-20 00:09:55 -07:00
}
}
2020-01-23 19:42:46 -08:00
/ * *
* Lint files and strings specified in the Options object .
*
2023-01-29 21:13:17 -08:00
* @ param { Options | null } options Options object .
2020-01-23 19:42:46 -08:00
* @ param { boolean } synchronous Whether to execute synchronously .
2023-09-04 21:41:16 -07:00
* @ param { LintCallback } callback Callback ( err , result ) function .
2020-01-23 19:42:46 -08:00
* @ returns { void }
* /
2017-12-15 22:55:51 -08:00
function lintInput ( options , synchronous , callback ) {
2015-03-15 23:39:17 -07:00
// Normalize inputs
2015-02-27 22:06:54 -08:00
options = options || { } ;
2015-03-11 21:13:21 -07:00
callback = callback || function noop ( ) { } ;
2023-07-11 22:17:53 -07:00
const customRuleList =
[ options . customRules || [ ] ]
. flat ( )
2023-07-12 21:58:36 -07:00
. map ( ( rule ) => ( {
"names" : helpers . cloneIfArray ( rule . names ) ,
"description" : rule . description ,
"information" : helpers . cloneIfUrl ( rule . information ) ,
"tags" : helpers . cloneIfArray ( rule . tags ) ,
2024-03-09 16:17:50 -08:00
"parser" : rule . parser ,
2023-07-12 21:58:36 -07:00
"asynchronous" : rule . asynchronous ,
"function" : rule . function
} ) ) ;
2021-02-06 19:55:22 -08:00
// eslint-disable-next-line unicorn/prefer-spread
2023-07-11 22:17:53 -07:00
const ruleList = rules . concat ( customRuleList ) ;
2021-12-11 21:44:25 -08:00
const ruleErr = validateRuleList ( ruleList , synchronous ) ;
2018-02-25 16:04:13 -08:00
if ( ruleErr ) {
2022-06-11 22:40:45 -07:00
callback ( ruleErr ) ;
return ;
2018-02-25 16:04:13 -08:00
}
2018-04-27 22:05:34 -07:00
let files = [ ] ;
2016-01-15 22:00:34 -08:00
if ( Array . isArray ( options . files ) ) {
Update dependencies: c8 to 7.7.2, eslint to 7.28.0, eslint-plugin-jsdoc to 35.1.3, eslint-plugin-unicorn to 33.0.1, globby to 11.0.3, js-yaml to 4.1.0, markdown-it-texmath to 0.9.0, markdownlint-rule-helpers to 0.14.0, ts-loader to 9.2.3, typescript to 4.3.2, webpack to 5.38.1, webpack-cli to 4.7.2.
2021-06-08 22:20:13 -07:00
files = [ ... options . files ] ;
2016-01-15 22:00:34 -08:00
} else if ( options . files ) {
files = [ String ( options . files ) ] ;
}
2018-04-27 22:05:34 -07:00
const strings = options . strings || { } ;
const stringsKeys = Object . keys ( strings ) ;
const config = options . config || { "default" : true } ;
2025-02-28 20:25:44 -08:00
const configParsers = options . configParsers || undefined ;
2018-04-27 22:05:34 -07:00
const frontMatter = ( options . frontMatter === undefined ) ?
2024-09-01 16:16:05 -07:00
helpers . frontMatterRe :
options . frontMatter ;
2019-05-18 12:32:52 -07:00
const handleRuleFailures = ! ! options . handleRuleFailures ;
2018-04-27 22:05:34 -07:00
const noInlineConfig = ! ! options . noInlineConfig ;
const resultVersion = ( options . resultVersion === undefined ) ?
2024-09-01 16:16:05 -07:00
3 :
options . resultVersion ;
2024-12-25 20:42:32 -08:00
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." ) ; } ) ;
2024-11-28 20:36:44 -08:00
const fs = options . fs || nodeFs ;
2023-03-12 20:49:41 -07:00
const aliasToRuleNames = mapAliasToRuleNames ( ruleList ) ;
2018-04-27 22:05:34 -07:00
const results = newResults ( ruleList ) ;
2020-09-12 12:01:20 -07:00
let done = false ;
2021-12-03 22:43:58 -08:00
let concurrency = 0 ;
2020-01-23 19:42:46 -08:00
// eslint-disable-next-line jsdoc/require-jsdoc
2021-12-03 22:43:58 -08:00
function lintWorker ( ) {
let currentItem = null ;
// eslint-disable-next-line jsdoc/require-jsdoc
function lintWorkerCallback ( err , result ) {
concurrency -- ;
if ( err ) {
done = true ;
return callback ( err ) ;
}
results [ currentItem ] = result ;
if ( ! synchronous ) {
lintWorker ( ) ;
}
return null ;
2018-02-25 16:04:13 -08:00
}
2021-12-03 22:43:58 -08:00
if ( done ) {
// Abort for error or nothing left to do
} else if ( files . length > 0 ) {
// Lint next file
concurrency ++ ;
currentItem = files . shift ( ) ;
2020-09-12 12:01:20 -07:00
lintFile (
ruleList ,
2023-03-12 20:49:41 -07:00
aliasToRuleNames ,
2021-12-03 22:43:58 -08:00
currentItem ,
2024-12-25 20:42:32 -08:00
markdownItFactory ,
2020-09-12 12:01:20 -07:00
config ,
2022-06-05 22:32:22 -07:00
configParsers ,
2020-09-12 12:01:20 -07:00
frontMatter ,
handleRuleFailures ,
noInlineConfig ,
resultVersion ,
2021-08-12 19:38:03 -07:00
fs ,
2020-09-12 12:01:20 -07:00
synchronous ,
2021-12-03 22:43:58 -08:00
lintWorkerCallback
2020-09-12 12:01:20 -07:00
) ;
2022-06-11 22:40:45 -07:00
} else if ( ( currentItem = stringsKeys . shift ( ) ) ) {
2021-12-03 22:43:58 -08:00
// Lint next string
2020-09-12 12:01:20 -07:00
concurrency ++ ;
2021-12-03 22:43:58 -08:00
lintContent (
2020-09-12 12:01:20 -07:00
ruleList ,
2023-03-12 20:49:41 -07:00
aliasToRuleNames ,
2021-12-03 22:43:58 -08:00
currentItem ,
strings [ currentItem ] || "" ,
2024-12-25 20:42:32 -08:00
markdownItFactory ,
2020-09-12 12:01:20 -07:00
config ,
2022-06-05 22:32:22 -07:00
configParsers ,
2020-09-12 12:01:20 -07:00
frontMatter ,
handleRuleFailures ,
noInlineConfig ,
resultVersion ,
2024-12-27 21:22:14 -08:00
synchronous ,
2021-12-03 22:43:58 -08:00
lintWorkerCallback
2020-09-12 12:01:20 -07:00
) ;
} else if ( concurrency === 0 ) {
2021-12-03 22:43:58 -08:00
// Finish
2020-09-12 12:01:20 -07:00
done = true ;
return callback ( null , results ) ;
2015-02-24 18:40:37 -08:00
}
2020-09-12 12:01:20 -07:00
return null ;
2015-02-24 18:40:37 -08:00
}
2021-12-03 22:43:58 -08:00
if ( synchronous ) {
while ( ! done ) {
lintWorker ( ) ;
}
} else {
// Testing on a Raspberry Pi 4 Model B with an artificial 5ms file access
// delay suggests that a concurrency factor of 8 can eliminate the impact
// of that delay (i.e., total time is the same as with no delay).
lintWorker ( ) ;
lintWorker ( ) ;
lintWorker ( ) ;
lintWorker ( ) ;
lintWorker ( ) ;
lintWorker ( ) ;
lintWorker ( ) ;
lintWorker ( ) ;
}
2017-12-15 22:55:51 -08:00
}
/ * *
* Lint specified Markdown files .
*
2023-01-29 21:13:17 -08:00
* @ param { Options | null } options Configuration options .
2019-11-10 19:26:55 -08:00
* @ param { LintCallback } callback Callback ( err , result ) function .
2017-12-15 22:55:51 -08:00
* @ returns { void }
* /
2024-12-03 19:58:28 -08:00
export function lintAsync ( options , callback ) {
2017-12-15 22:55:51 -08:00
return lintInput ( options , false , callback ) ;
2015-03-20 00:09:55 -07:00
}
2020-09-13 12:58:09 -07:00
/ * *
* Lint specified Markdown files .
*
2024-12-03 19:58:28 -08:00
* @ param { Options | null } options Configuration options .
2020-09-13 12:58:09 -07:00
* @ returns { Promise < LintResults > } Results object .
* /
2024-12-03 19:58:28 -08:00
export function lintPromise ( options ) {
2024-11-30 21:44:37 -08:00
return new Promise ( ( resolve , reject ) => {
2024-12-03 19:58:28 -08:00
lintAsync ( options , ( error , results ) => {
2024-11-30 21:44:37 -08:00
if ( error || ! results ) {
reject ( error ) ;
} else {
resolve ( results ) ;
}
} ) ;
} ) ;
2020-09-13 12:58:09 -07:00
}
2015-03-20 00:09:55 -07:00
/ * *
2024-12-03 19:58:28 -08:00
* Lint specified Markdown files .
2015-03-20 00:09:55 -07:00
*
2023-01-29 21:13:17 -08:00
* @ param { Options | null } options Configuration options .
2019-11-10 19:26:55 -08:00
* @ returns { LintResults } Results object .
2015-03-20 00:09:55 -07:00
* /
2024-12-03 19:58:28 -08:00
export function lintSync ( options ) {
2023-09-04 21:41:16 -07:00
let results = null ;
2017-12-15 22:55:51 -08:00
lintInput ( options , true , function callback ( error , res ) {
2018-02-25 16:04:13 -08:00
if ( error ) {
throw error ;
}
2017-12-15 22:55:51 -08:00
results = res ;
} ) ;
2022-06-11 22:40:45 -07:00
// @ts-ignore
2017-12-15 22:55:51 -08:00
return results ;
2015-03-20 00:09:55 -07:00
}
2021-08-12 20:43:18 -07:00
/ * *
* 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 .
2022-06-11 22:40:45 -07:00
* @ param { ResolveConfigExtendsCallback } callback Callback ( err , result )
2021-08-12 20:43:18 -07:00
* function .
* @ returns { void }
* /
function resolveConfigExtends ( configFile , referenceId , fs , callback ) {
const configFileDirname = path . dirname ( configFile ) ;
const resolvedExtendsFile = path . resolve ( configFileDirname , referenceId ) ;
fs . access ( resolvedExtendsFile , ( err ) => {
if ( err ) {
// Not a file, try require.resolve
try {
2025-01-12 18:27:14 -08:00
return callback (
null ,
resolveModule ( referenceId , [ configFileDirname ] )
) ;
2021-08-12 20:43:18 -07:00
} catch {
// Unable to resolve, use resolvedExtendsFile
}
}
return callback ( null , resolvedExtendsFile ) ;
} ) ;
}
2020-10-22 04:40:54 +01:00
/ * *
* Resolve referenced "extends" path in a configuration file
2020-10-21 20:53:30 -07:00
* using path . resolve ( ) with require . resolve ( ) as a fallback .
2020-10-22 04:40:54 +01:00
*
* @ param { string } configFile Configuration file name .
* @ param { string } referenceId Referenced identifier to resolve .
2021-08-12 19:38:03 -07:00
* @ param { Object } fs File system implementation .
2020-10-22 04:40:54 +01:00
* @ returns { string } Resolved path to file .
* /
2021-08-12 20:43:18 -07:00
function resolveConfigExtendsSync ( configFile , referenceId , fs ) {
2020-10-22 04:40:54 +01:00
const configFileDirname = path . dirname ( configFile ) ;
const resolvedExtendsFile = path . resolve ( configFileDirname , referenceId ) ;
try {
2021-08-12 19:38:03 -07:00
fs . accessSync ( resolvedExtendsFile ) ;
return resolvedExtendsFile ;
2020-10-21 20:53:30 -07:00
} catch {
2021-08-12 19:38:03 -07:00
// Not a file, try require.resolve
2020-10-22 04:40:54 +01:00
}
try {
2025-01-12 18:27:14 -08:00
return resolveModule ( referenceId , [ configFileDirname ] ) ;
2020-10-21 20:53:30 -07:00
} catch {
2021-08-12 19:38:03 -07:00
// Unable to resolve, return resolvedExtendsFile
2020-10-22 04:40:54 +01:00
}
return resolvedExtendsFile ;
}
2023-04-03 22:59:06 -07:00
/ * *
* Extend specified configuration object .
*
* @ param { Configuration } config Configuration object .
* @ param { string } file Configuration file name .
2024-11-30 21:44:37 -08:00
* @ param { ConfigurationParser [ ] | undefined } parsers Parsing
2023-04-03 22:59:06 -07:00
* function ( s ) .
* @ param { Object } fs File system implementation .
* @ param { ReadConfigCallback } callback Callback ( err , result ) function .
* @ returns { void }
* /
function extendConfig ( config , file , parsers , fs , callback ) {
const configExtends = config . extends ;
if ( configExtends ) {
return resolveConfigExtends (
file ,
2024-11-28 20:36:44 -08:00
helpers . expandTildePath ( configExtends , os ) ,
2023-04-03 22:59:06 -07:00
fs ,
// eslint-disable-next-line no-use-before-define
2024-12-03 19:58:28 -08:00
( _ , resolvedExtends ) => readConfigAsync (
2023-04-03 22:59:06 -07:00
// @ts-ignore
resolvedExtends ,
parsers ,
fs ,
( err , extendsConfig ) => {
if ( err ) {
return callback ( err ) ;
}
const result = {
... extendsConfig ,
... config
} ;
delete result . extends ;
return callback ( null , result ) ;
}
)
) ;
}
return callback ( null , config ) ;
}
/ * *
* Extend specified configuration object .
*
* @ param { Configuration } config Configuration object .
* @ param { string } file Configuration file name .
2024-11-30 21:44:37 -08:00
* @ param { ConfigurationParser [ ] | undefined } parsers Parsing function ( s ) .
* @ param { Object } fs File system implementation .
2023-04-03 22:59:06 -07:00
* @ returns { Promise < Configuration > } Configuration object .
* /
2024-12-03 19:58:28 -08:00
export function extendConfigPromise ( config , file , parsers , fs ) {
2024-11-30 21:44:37 -08:00
return new Promise ( ( resolve , reject ) => {
extendConfig ( config , file , parsers , fs , ( error , results ) => {
if ( error || ! results ) {
reject ( error ) ;
} else {
resolve ( results ) ;
}
} ) ;
} ) ;
2023-04-03 22:59:06 -07:00
}
2017-05-19 22:36:46 -07:00
/ * *
* Read specified configuration file .
*
2019-11-10 19:26:55 -08:00
* @ param { string } file Configuration file name .
2024-11-30 21:44:37 -08:00
* @ param { ConfigurationParser [ ] | ReadConfigCallback } [ parsers ] Parsing
2020-01-19 21:01:11 -08:00
* function ( s ) .
2021-08-12 19:38:03 -07:00
* @ param { Object } [ fs ] File system implementation .
2020-01-11 20:48:00 -08:00
* @ param { ReadConfigCallback } [ callback ] Callback ( err , result ) function .
2017-05-19 22:36:46 -07:00
* @ returns { void }
* /
2024-12-03 19:58:28 -08:00
export function readConfigAsync ( file , parsers , fs , callback ) {
2018-05-23 22:24:40 -07:00
if ( ! callback ) {
2021-08-12 19:38:03 -07:00
if ( fs ) {
callback = fs ;
fs = null ;
} else {
// @ts-ignore
callback = parsers ;
2022-06-11 22:40:45 -07:00
// @ts-ignore
2021-08-12 19:38:03 -07:00
parsers = null ;
}
}
if ( ! fs ) {
2024-11-28 20:36:44 -08:00
fs = nodeFs ;
2018-05-23 22:24:40 -07:00
}
2017-05-19 22:36:46 -07:00
// Read file
2024-11-28 20:36:44 -08:00
file = helpers . expandTildePath ( file , os ) ;
2021-08-05 22:01:29 -07:00
fs . readFile ( file , "utf8" , ( err , content ) => {
2017-05-19 22:36:46 -07:00
if ( err ) {
2022-06-11 22:40:45 -07:00
// @ts-ignore
2017-05-19 22:36:46 -07:00
return callback ( err ) ;
}
2018-05-23 22:24:40 -07:00
// Try to parse file
2020-01-23 19:42:46 -08:00
// @ts-ignore
2018-05-23 22:24:40 -07:00
const { config , message } = parseConfiguration ( file , content , parsers ) ;
if ( ! config ) {
2022-06-11 22:40:45 -07:00
// @ts-ignore
2018-05-23 22:24:40 -07:00
return callback ( new Error ( message ) ) ;
2017-05-19 22:36:46 -07:00
}
2018-05-23 22:24:40 -07:00
// Extend configuration
2022-06-11 22:40:45 -07:00
// @ts-ignore
2023-04-03 22:59:06 -07:00
return extendConfig ( config , file , parsers , fs , callback ) ;
2017-05-19 22:36:46 -07:00
} ) ;
}
2020-09-13 12:58:09 -07:00
/ * *
* Read specified configuration file .
*
* @ param { string } file Configuration file name .
* @ param { ConfigurationParser [ ] } [ parsers ] Parsing function ( s ) .
2021-08-12 19:38:03 -07:00
* @ param { Object } [ fs ] File system implementation .
2020-09-13 12:58:09 -07:00
* @ returns { Promise < Configuration > } Configuration object .
* /
2024-12-03 19:58:28 -08:00
export function readConfigPromise ( file , parsers , fs ) {
2024-11-30 21:44:37 -08:00
return new Promise ( ( resolve , reject ) => {
2024-12-03 19:58:28 -08:00
readConfigAsync ( file , parsers , fs , ( error , results ) => {
2024-11-30 21:44:37 -08:00
if ( error || ! results ) {
reject ( error ) ;
} else {
resolve ( results ) ;
}
} ) ;
} ) ;
2020-09-13 12:58:09 -07:00
}
2017-05-19 22:36:46 -07:00
/ * *
2024-12-03 19:58:28 -08:00
* Read specified configuration file .
2017-05-19 22:36:46 -07:00
*
2019-11-10 19:26:55 -08:00
* @ param { string } file Configuration file name .
* @ param { ConfigurationParser [ ] } [ parsers ] Parsing function ( s ) .
2021-08-12 19:38:03 -07:00
* @ param { Object } [ fs ] File system implementation .
2019-11-10 19:26:55 -08:00
* @ returns { Configuration } Configuration object .
2017-05-19 22:36:46 -07:00
* /
2024-12-03 19:58:28 -08:00
export function readConfigSync ( file , parsers , fs ) {
2021-08-12 19:38:03 -07:00
if ( ! fs ) {
2024-11-28 20:36:44 -08:00
fs = nodeFs ;
2021-08-12 19:38:03 -07:00
}
2018-05-23 22:24:40 -07:00
// Read file
2022-05-16 22:57:11 -07:00
file = helpers . expandTildePath ( file , os ) ;
2021-08-05 22:01:29 -07:00
const content = fs . readFileSync ( file , "utf8" ) ;
2018-05-23 22:24:40 -07:00
// Try to parse file
const { config , message } = parseConfiguration ( file , content , parsers ) ;
if ( ! config ) {
2025-03-01 18:23:48 -08:00
// @ts-ignore
2018-05-23 22:24:40 -07:00
throw new Error ( message ) ;
}
// Extend configuration
const configExtends = config . extends ;
if ( configExtends ) {
2017-05-19 22:36:46 -07:00
delete config . extends ;
2022-05-16 22:57:11 -07:00
const resolvedExtends = resolveConfigExtendsSync (
file ,
helpers . expandTildePath ( configExtends , os ) ,
fs
) ;
2019-05-05 22:27:01 -07:00
return {
2021-08-12 19:38:03 -07:00
... readConfigSync ( resolvedExtends , parsers , fs ) ,
2019-05-05 22:27:01 -07:00
... config
} ;
2017-05-19 22:36:46 -07:00
}
return config ;
}
2024-10-06 17:24:44 -07:00
/ * *
* Normalizes the fields of a RuleOnErrorFixInfo instance .
*
* @ param { RuleOnErrorFixInfo } fixInfo RuleOnErrorFixInfo instance .
* @ param { number } [ lineNumber ] Line number .
* @ returns { RuleOnErrorFixInfoNormalized } Normalized RuleOnErrorFixInfo instance .
* /
function normalizeFixInfo ( fixInfo , lineNumber = 0 ) {
return {
"lineNumber" : fixInfo . lineNumber || lineNumber ,
"editColumn" : fixInfo . editColumn || 1 ,
"deleteCount" : fixInfo . deleteCount || 0 ,
"insertText" : fixInfo . insertText || ""
} ;
}
/ * *
* Applies the specified fix to a Markdown content line .
*
* @ param { string } line Line of Markdown content .
* @ param { RuleOnErrorFixInfo } fixInfo RuleOnErrorFixInfo instance .
* @ param { string } [ lineEnding ] Line ending to use .
* @ returns { string | null } Fixed content or null if deleted .
* /
2024-12-03 19:58:28 -08:00
export function applyFix ( line , fixInfo , lineEnding = "\n" ) {
2024-10-06 17:24:44 -07:00
const { editColumn , deleteCount , insertText } = normalizeFixInfo ( fixInfo ) ;
const editIndex = editColumn - 1 ;
return ( deleteCount === - 1 ) ?
null :
line . slice ( 0 , editIndex ) + insertText . replace ( /\n/g , lineEnding ) + line . slice ( editIndex + deleteCount ) ;
}
/ * *
* Applies as many of the specified fixes as possible to Markdown content .
*
* @ param { string } input Lines of Markdown content .
* @ param { RuleOnErrorInfo [ ] } errors RuleOnErrorInfo instances .
* @ returns { string } Fixed content .
* /
2024-12-03 19:58:28 -08:00
export function applyFixes ( input , errors ) {
2024-11-28 20:36:44 -08:00
const lineEnding = helpers . getPreferredLineEnding ( input , os ) ;
2024-10-06 17:24:44 -07:00
const lines = input . split ( helpers . newLineRe ) ;
// Normalize fixInfo objects
let fixInfos = errors
. filter ( ( error ) => error . fixInfo )
// @ts-ignore
. map ( ( error ) => normalizeFixInfo ( error . fixInfo , error . lineNumber ) ) ;
// Sort bottom-to-top, line-deletes last, right-to-left, long-to-short
fixInfos . sort ( ( a , b ) => {
const aDeletingLine = ( a . deleteCount === - 1 ) ;
const bDeletingLine = ( b . deleteCount === - 1 ) ;
return (
( b . lineNumber - a . lineNumber ) ||
( aDeletingLine ? 1 : ( bDeletingLine ? - 1 : 0 ) ) ||
( b . editColumn - a . editColumn ) ||
( b . insertText . length - a . insertText . length )
) ;
} ) ;
// Remove duplicate entries (needed for following collapse step)
2024-11-28 20:36:44 -08:00
/** @type {RuleOnErrorFixInfo} */
2024-10-06 17:24:44 -07:00
let lastFixInfo = { } ;
fixInfos = fixInfos . filter ( ( fixInfo ) => {
const unique = (
( fixInfo . lineNumber !== lastFixInfo . lineNumber ) ||
( fixInfo . editColumn !== lastFixInfo . editColumn ) ||
( fixInfo . deleteCount !== lastFixInfo . deleteCount ) ||
( fixInfo . insertText !== lastFixInfo . insertText )
) ;
lastFixInfo = fixInfo ;
return unique ;
} ) ;
// Collapse insert/no-delete and no-insert/delete for same line/column
lastFixInfo = {
"lineNumber" : - 1
} ;
for ( const fixInfo of fixInfos ) {
if (
( fixInfo . lineNumber === lastFixInfo . lineNumber ) &&
( fixInfo . editColumn === lastFixInfo . editColumn ) &&
! fixInfo . insertText &&
( fixInfo . deleteCount > 0 ) &&
lastFixInfo . insertText &&
! lastFixInfo . deleteCount ) {
fixInfo . insertText = lastFixInfo . insertText ;
lastFixInfo . lineNumber = 0 ;
}
lastFixInfo = fixInfo ;
}
fixInfos = fixInfos . filter ( ( fixInfo ) => fixInfo . lineNumber ) ;
// Apply all (remaining/updated) fixes
let lastLineIndex = - 1 ;
let lastEditIndex = - 1 ;
for ( const fixInfo of fixInfos ) {
const { lineNumber , editColumn , deleteCount } = fixInfo ;
const lineIndex = lineNumber - 1 ;
const editIndex = editColumn - 1 ;
if (
( lineIndex !== lastLineIndex ) ||
( deleteCount === - 1 ) ||
( ( editIndex + deleteCount ) <=
( lastEditIndex - ( ( deleteCount > 0 ) ? 0 : 1 ) ) )
) {
// @ts-ignore
lines [ lineIndex ] = applyFix ( lines [ lineIndex ] , fixInfo , lineEnding ) ;
}
lastLineIndex = lineIndex ;
lastEditIndex = editIndex ;
}
// Return corrected input
return lines . filter ( ( line ) => line !== null ) . join ( lineEnding ) ;
}
2020-10-17 14:17:35 -07:00
/ * *
* Gets the ( semantic ) version of the library .
*
* @ returns { string } SemVer string .
* /
2024-12-03 19:58:28 -08:00
export function getVersion ( ) {
2024-09-29 18:11:41 -07:00
return version ;
2020-10-17 14:17:35 -07:00
}
2019-11-10 19:26:55 -08:00
// Type declarations
/ * *
* Function to implement rule logic .
*
* @ callback RuleFunction
* @ param { RuleParams } params Rule parameters .
* @ param { RuleOnError } onError Error - reporting callback .
* @ returns { void }
* /
2024-03-09 16:17:50 -08:00
/* eslint-disable jsdoc/valid-types */
2019-11-10 19:26:55 -08:00
/ * *
* Rule parameters .
*
* @ typedef { Object } RuleParams
* @ property { string } name File / string name .
2024-02-27 20:42:09 -08:00
* @ property { MarkdownParsers } parsers Markdown parser data .
2024-03-09 16:17:50 -08:00
* @ property { readonly string [ ] } lines File / string lines .
* @ property { readonly string [ ] } frontMatterLines Front matter lines .
2019-11-10 19:26:55 -08:00
* @ property { RuleConfiguration } config Rule configuration .
2024-09-29 18:11:41 -07:00
* @ property { string } version Version of the markdownlint library .
2019-11-10 19:26:55 -08:00
* /
2024-03-09 16:17:50 -08:00
/* eslint-enable jsdoc/valid-types */
2019-11-10 19:26:55 -08:00
/ * *
2024-02-27 20:42:09 -08:00
* Markdown parser data .
*
* @ typedef { Object } MarkdownParsers
2024-03-09 16:17:50 -08:00
* @ property { ParserMarkdownIt } markdownit Markdown parser data from markdown - it ( only present when Rule . parser is "markdownit" ) .
* @ property { ParserMicromark } micromark Markdown parser data from micromark ( only present when Rule . parser is "micromark" ) .
2024-02-27 20:42:09 -08:00
* /
/ * *
* Markdown parser data from markdown - it .
*
* @ typedef { Object } ParserMarkdownIt
* @ property { MarkdownItToken [ ] } tokens Token objects from markdown - it .
* /
2024-03-09 16:17:50 -08:00
/ * *
* Markdown parser data from micromark .
*
* @ typedef { Object } ParserMicromark
* @ property { MicromarkToken [ ] } tokens Token objects from micromark .
* /
2024-02-27 20:42:09 -08:00
/ * *
* markdown - it token .
2019-11-10 19:26:55 -08:00
*
* @ typedef { Object } MarkdownItToken
* @ property { string [ ] [ ] } attrs HTML attributes .
* @ property { boolean } block Block - level token .
* @ property { MarkdownItToken [ ] } children Child nodes .
* @ property { string } content Tag contents .
* @ property { boolean } hidden Ignore element .
* @ property { string } info Fence info .
* @ property { number } level Nesting level .
* @ property { number [ ] } map Beginning / ending line numbers .
* @ property { string } markup Markup text .
* @ property { Object } meta Arbitrary data .
* @ property { number } nesting Level change .
* @ property { string } tag HTML tag name .
* @ property { string } type Token type .
2019-11-11 21:09:37 -08:00
* @ property { number } lineNumber Line number ( 1 - based ) .
* @ property { string } line Line content .
2019-11-10 19:26:55 -08:00
* /
2024-11-28 20:36:44 -08:00
/** @typedef {import("micromark-util-types").TokenType} MicromarkTokenType */
2024-03-09 16:17:50 -08:00
/ * *
* micromark token .
*
* @ typedef { Object } MicromarkToken
* @ property { MicromarkTokenType } type Token type .
* @ property { number } startLine Start line ( 1 - based ) .
* @ property { number } startColumn Start column ( 1 - based ) .
* @ property { number } endLine End line ( 1 - based ) .
* @ property { number } endColumn End column ( 1 - based ) .
* @ property { string } text Token text .
* @ property { MicromarkToken [ ] } children Child tokens .
* @ property { MicromarkToken | null } parent Parent token .
* /
2019-11-10 19:26:55 -08:00
/ * *
* Error - reporting callback .
*
* @ callback RuleOnError
* @ param { RuleOnErrorInfo } onErrorInfo Error information .
* @ returns { void }
* /
/ * *
* Fix information for RuleOnError callback .
*
* @ typedef { Object } RuleOnErrorInfo
* @ property { number } lineNumber Line number ( 1 - based ) .
2021-09-25 16:23:37 -07:00
* @ property { string } [ detail ] Detail about the error .
2019-11-10 19:26:55 -08:00
* @ property { string } [ context ] Context for the error .
2023-07-11 21:44:45 -07:00
* @ property { URL } [ information ] Link to more information .
2019-11-10 19:26:55 -08:00
* @ property { number [ ] } [ range ] Column number ( 1 - based ) and length .
* @ property { RuleOnErrorFixInfo } [ fixInfo ] Fix information .
* /
/ * *
* Fix information for RuleOnErrorInfo .
*
* @ typedef { Object } RuleOnErrorFixInfo
* @ property { number } [ lineNumber ] Line number ( 1 - based ) .
* @ property { number } [ editColumn ] Column of the fix ( 1 - based ) .
* @ property { number } [ deleteCount ] Count of characters to delete .
* @ property { string } [ insertText ] Text to insert ( after deleting ) .
* /
2024-10-06 17:24:44 -07:00
/ * *
* RuleOnErrorInfo with all optional properties present .
*
* @ typedef { Object } RuleOnErrorFixInfoNormalized
* @ property { number } lineNumber Line number ( 1 - based ) .
* @ property { number } editColumn Column of the fix ( 1 - based ) .
* @ property { number } deleteCount Count of characters to delete .
* @ property { string } insertText Text to insert ( after deleting ) .
* /
2019-11-10 19:26:55 -08:00
/ * *
* Rule definition .
*
* @ typedef { Object } Rule
* @ property { string [ ] } names Rule name ( s ) .
* @ property { string } description Rule description .
* @ property { URL } [ information ] Link to more information .
* @ property { string [ ] } tags Rule tag ( s ) .
2024-03-09 16:17:50 -08:00
* @ property { "markdownit" | "micromark" | "none" } parser Parser used .
2021-12-11 21:44:25 -08:00
* @ property { boolean } [ asynchronous ] True if asynchronous .
2019-11-10 19:26:55 -08:00
* @ property { RuleFunction } function Rule implementation .
* /
2024-12-25 20:42:32 -08:00
/ * *
* Method used by the markdown - it parser to parse input .
*
* @ callback MarkdownItParse
* @ param { string } src Source string .
* @ param { Object } env Environment sandbox .
* @ returns { import ( "markdown-it" ) . Token [ ] } Tokens .
* /
/ * *
* Instance of the markdown - it parser .
*
* @ typedef MarkdownIt
* @ property { MarkdownItParse } parse Method to parse input .
* /
/ * *
* Gets an instance of the markdown - it parser . Any plugins should already have been loaded .
*
* @ callback MarkdownItFactory
2024-12-27 21:22:14 -08:00
* @ returns { MarkdownIt | Promise < MarkdownIt > } Instance of the markdown - it parser .
2024-12-25 20:42:32 -08:00
* /
2019-11-10 19:26:55 -08:00
/ * *
* Configuration options .
*
* @ typedef { Object } Options
* @ property { Configuration } [ config ] Configuration object .
2022-06-05 22:32:22 -07:00
* @ property { ConfigurationParser [ ] } [ configParsers ] Configuration parsers .
2019-11-10 19:26:55 -08:00
* @ property { Rule [ ] | Rule } [ customRules ] Custom rules .
2022-06-05 22:32:22 -07:00
* @ property { string [ ] | string } [ files ] Files to lint .
2023-01-29 21:13:17 -08:00
* @ property { RegExp | null } [ frontMatter ] Front matter pattern .
2022-06-05 22:32:22 -07:00
* @ property { Object } [ fs ] File system implementation .
2019-11-10 19:26:55 -08:00
* @ property { boolean } [ handleRuleFailures ] True to catch exceptions .
2024-12-25 20:42:32 -08:00
* @ property { MarkdownItFactory } [ markdownItFactory ] Function to create a markdown - it parser .
2019-11-10 19:26:55 -08:00
* @ property { boolean } [ noInlineConfig ] True to ignore HTML directives .
* @ property { number } [ resultVersion ] Results object version .
2022-06-05 22:32:22 -07:00
* @ property { Object . < string , string > } [ strings ] Strings to lint .
2019-11-10 19:26:55 -08:00
* /
/ * *
2020-11-24 16:25:08 -08:00
* A markdown - it plugin .
2019-11-10 19:26:55 -08:00
*
* @ typedef { Array } Plugin
* /
/ * *
* Function to pretty - print lint results .
*
* @ callback ToStringCallback
* @ param { boolean } [ ruleAliases ] True to use rule aliases .
2024-12-25 20:42:32 -08:00
* @ returns { string } Pretty - printed results .
2019-11-10 19:26:55 -08:00
* /
/ * *
* Lint results ( for resultVersion 3 ) .
*
* @ typedef { Object . < string , LintError [ ] > } LintResults
2020-09-13 12:58:09 -07:00
* @ property { ToStringCallback } toString String representation .
2019-11-10 19:26:55 -08:00
* /
/ * *
* Lint error .
*
* @ typedef { Object } LintError
* @ 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 [ ] } errorRange Column number ( 1 - based ) and length .
2021-01-10 20:46:00 -08:00
* @ property { FixInfo } [ fixInfo ] Fix information .
2019-11-10 19:26:55 -08:00
* /
/ * *
* Fix information .
*
* @ typedef { Object } FixInfo
2021-06-14 22:30:35 -07:00
* @ property { number } [ lineNumber ] Line number ( 1 - based ) .
2019-11-10 19:26:55 -08:00
* @ property { number } [ editColumn ] Column of the fix ( 1 - based ) .
* @ property { number } [ deleteCount ] Count of characters to delete .
* @ property { string } [ insertText ] Text to insert ( after deleting ) .
* /
2023-09-04 21:41:16 -07:00
/ * *
* Called with the result of linting a string or document .
*
* @ callback LintContentCallback
* @ param { Error | null } error Error iff failed .
* @ param { LintError [ ] } [ result ] Result iff successful .
* @ returns { void }
* /
2019-11-10 19:26:55 -08:00
/ * *
2021-08-12 20:43:18 -07:00
* Called with the result of the lint function .
2019-11-10 19:26:55 -08:00
*
* @ callback LintCallback
2023-09-04 21:41:16 -07:00
* @ param { Error | null } error Error object iff failed .
* @ param { LintResults } [ results ] Lint results iff succeeded .
2019-11-10 19:26:55 -08:00
* @ returns { void }
* /
/ * *
2023-11-08 19:49:02 -08:00
* Configuration object for linting rules . For the JSON schema , see
2019-11-10 19:26:55 -08:00
* { @ link . . / schema / markdownlint - config - schema . json } .
*
2024-11-28 20:36:44 -08:00
* @ typedef { import ( "./configuration.d.ts" ) . Configuration } Configuration
2019-11-10 19:26:55 -08:00
* /
2024-08-27 20:47:33 -07:00
/ * *
* Configuration object for linting rules strictly . For the JSON schema , see
* { @ link . . / schema / markdownlint - config - schema - strict . json } .
*
2024-11-28 20:36:44 -08:00
* @ typedef { import ( "./configuration-strict.d.ts" ) . ConfigurationStrict } ConfigurationStrict
2024-08-27 20:47:33 -07:00
* /
2019-11-10 19:26:55 -08:00
/ * *
* Rule configuration object .
*
* @ typedef { boolean | Object } RuleConfiguration Rule configuration .
* /
/ * *
* Parses a configuration string and returns a configuration object .
*
* @ callback ConfigurationParser
* @ param { string } text Configuration string .
* @ returns { Configuration }
* /
/ * *
2021-08-12 20:43:18 -07:00
* Called with the result of the readConfig function .
2019-11-10 19:26:55 -08:00
*
* @ callback ReadConfigCallback
* @ param { Error | null } err Error object or null .
* @ param { Configuration } [ config ] Configuration object .
* @ returns { void }
* /
2021-08-12 20:43:18 -07:00
/ * *
* Called with the result of the resolveConfigExtends function .
*
* @ callback ResolveConfigExtendsCallback
* @ param { Error | null } err Error object or null .
* @ param { string } [ path ] Resolved path to file .
* @ returns { void }
* /