diff --git a/markdownlint-cli2.js b/markdownlint-cli2.js index eaa10927..e762f844 100755 --- a/markdownlint-cli2.js +++ b/markdownlint-cli2.js @@ -69,19 +69,19 @@ const readConfig = (fs, dir, name, otherwise) => { }; // Import or resolve/require a module ID with a custom directory in the path -const importOrRequireResolve = async (dir, id) => { +const importOrRequireResolve = async (dirs, id) => { if (typeof id === "string") { const expandId = markdownlintRuleHelpers.expandTildePath(id, require("node:os")); const errors = []; try { - return resolveAndRequire(dynamicRequire, expandId, dir); + return resolveAndRequire(dynamicRequire, expandId, dirs); } catch (error) { errors.push(error); } try { const fileUrlString = - pathToFileURL(pathDefault.resolve(dir, expandId)).toString(); + pathToFileURL(pathDefault.resolve(dirs[0], expandId)).toString(); // eslint-disable-next-line no-inline-comments const module = await import(/* webpackIgnore: true */ fileUrlString); return module.default; @@ -98,31 +98,38 @@ const importOrRequireResolve = async (dir, id) => { }; // Import or require an array of modules by ID -const importOrRequireIds = (dir, ids, noRequire) => ( - Promise.all(noRequire ? [] : ids.map((id) => importOrRequireResolve(dir, id))) +const importOrRequireIds = (dirs, ids, noRequire) => ( + Promise.all( + noRequire ? [] : ids.map((id) => importOrRequireResolve(dirs, id)) + ) ); // Import or require an array of modules by ID (preserving parameters) -const importOrRequireIdsAndParams = async (dir, idsAndParams, noRequire) => { +const importOrRequireIdsAndParams = async (dirs, idsAndParams, noRequire) => { if (noRequire) { return []; } const ids = idsAndParams.map((entry) => entry[0]); - const modules = await importOrRequireIds(dir, ids); + const modules = await importOrRequireIds(dirs, ids, noRequire); const modulesAndParams = idsAndParams. map((entry, i) => [ modules[i], ...entry.slice(1) ]); return modulesAndParams; }; // Import or require a JavaScript file and return the exported object -const importOrRequireConfig = (fs, dir, name, noRequire, otherwise) => { - const id = pathPosix.join(dir, name); - return () => fs.promises.access(id). - then( - () => (noRequire ? {} : importOrRequireResolve(dir, id)), - otherwise - ); -}; +const importOrRequireConfig = + (fs, dir, name, modulePaths, noRequire, otherwise) => { + const id = pathPosix.join(dir, name); + return () => fs.promises.access(id). + then( + () => ( + noRequire + ? {} + : importOrRequireResolve([ dir, ...modulePaths ], id) + ), + otherwise + ); + }; // Extend a config object if it has 'extends' property const getExtendedConfig = async (config, configPath, fs) => { @@ -155,7 +162,7 @@ const readOptionsOrConfig = async (configPath, fs, noRequire) => { basename.endsWith(".markdownlint-cli2.mjs") ) { options = await ( - importOrRequireConfig(fs, dirname, basename, noRequire, noop)() + importOrRequireConfig(fs, dirname, basename, [], noRequire, noop)() ); } else if ( basename.endsWith(".markdownlint.jsonc") || @@ -171,7 +178,7 @@ const readOptionsOrConfig = async (configPath, fs, noRequire) => { basename.endsWith(".markdownlint.mjs") ) { config = await ( - importOrRequireConfig(fs, dirname, basename, noRequire, noop)() + importOrRequireConfig(fs, dirname, basename, [], noRequire, noop)() ); } else { throw new Error( @@ -272,129 +279,140 @@ $ markdownlint-cli2 "**/*.md" "#node_modules"` }; // Get (creating if necessary) and process a directory's info object -const getAndProcessDirInfo = - (fs, tasks, dirToDirInfo, dir, relativeDir, noRequire, allowPackageJson) => { - let dirInfo = dirToDirInfo[dir]; - if (!dirInfo) { - dirInfo = { - dir, - relativeDir, - "parent": null, - "files": [], - "markdownlintConfig": null, - "markdownlintOptions": null - }; - dirToDirInfo[dir] = dirInfo; - - // Load markdownlint-cli2 object(s) - const markdownlintCli2Jsonc = - pathPosix.join(dir, ".markdownlint-cli2.jsonc"); - const markdownlintCli2Yaml = - pathPosix.join(dir, ".markdownlint-cli2.yaml"); - const packageJson = pathPosix.join(dir, "package.json"); - tasks.push( - fs.promises.access(markdownlintCli2Jsonc). - then( - () => fs.promises. - readFile(markdownlintCli2Jsonc, utf8). - then( - (content) => getJsoncParse(). - then((jsoncParse) => jsoncParse(content)) - ), - () => fs.promises.access(markdownlintCli2Yaml). - then( - () => fs.promises. - readFile(markdownlintCli2Yaml, utf8). - then(yamlParse), +const getAndProcessDirInfo = ( + fs, + tasks, + dirToDirInfo, + dir, + relativeDir, + noRequire, + allowPackageJson +) => { + let dirInfo = dirToDirInfo[dir]; + if (!dirInfo) { + dirInfo = { + dir, + relativeDir, + "parent": null, + "files": [], + "markdownlintConfig": null, + "markdownlintOptions": null + }; + dirToDirInfo[dir] = dirInfo; + + // Load markdownlint-cli2 object(s) + const markdownlintCli2Jsonc = + pathPosix.join(dir, ".markdownlint-cli2.jsonc"); + const markdownlintCli2Yaml = + pathPosix.join(dir, ".markdownlint-cli2.yaml"); + const packageJson = pathPosix.join(dir, "package.json"); + tasks.push( + fs.promises.access(markdownlintCli2Jsonc). + then( + () => fs.promises. + readFile(markdownlintCli2Jsonc, utf8). + then( + (content) => getJsoncParse(). + then((jsoncParse) => jsoncParse(content)) + ), + () => fs.promises.access(markdownlintCli2Yaml). + then( + () => fs.promises. + readFile(markdownlintCli2Yaml, utf8). + then(yamlParse), + importOrRequireConfig( + fs, + dir, + ".markdownlint-cli2.cjs", + [], + noRequire, importOrRequireConfig( fs, dir, - ".markdownlint-cli2.cjs", + ".markdownlint-cli2.mjs", + [], noRequire, - importOrRequireConfig( - fs, - dir, - ".markdownlint-cli2.mjs", - noRequire, - () => (allowPackageJson - ? fs.promises.access(packageJson) - // eslint-disable-next-line prefer-promise-reject-errors - : Promise.reject() - ). - then( - () => fs.promises. - readFile(packageJson, utf8). - then( - (content) => getJsoncParse(). - then((jsoncParse) => jsoncParse(content)). - then((obj) => obj[packageName]) - ), - noop - ) - ) + () => (allowPackageJson + ? fs.promises.access(packageJson) + // eslint-disable-next-line prefer-promise-reject-errors + : Promise.reject() + ). + then( + () => fs.promises. + readFile(packageJson, utf8). + then( + (content) => getJsoncParse(). + then((jsoncParse) => jsoncParse(content)). + then((obj) => obj[packageName]) + ), + noop + ) ) ) - ). - then((options) => { - dirInfo.markdownlintOptions = options; - return options && - options.config && - getExtendedConfig( - options.config, - // Just needs to identify a file in the right directory - markdownlintCli2Jsonc, - fs - ). - then((config) => { - options.config = config; - }); - }) - ); + ) + ). + then((options) => { + dirInfo.markdownlintOptions = options; + return options && + options.config && + getExtendedConfig( + options.config, + // Just needs to identify a file in the right directory + markdownlintCli2Jsonc, + fs + ). + then((config) => { + options.config = config; + }); + }) + ); - // Load markdownlint object(s) - const readConfigs = + // Load markdownlint object(s) + const readConfigs = + readConfig( + fs, + dir, + ".markdownlint.jsonc", readConfig( fs, dir, - ".markdownlint.jsonc", + ".markdownlint.json", readConfig( fs, dir, - ".markdownlint.json", + ".markdownlint.yaml", readConfig( fs, dir, - ".markdownlint.yaml", - readConfig( + ".markdownlint.yml", + importOrRequireConfig( fs, dir, - ".markdownlint.yml", + ".markdownlint.cjs", + [], + noRequire, importOrRequireConfig( fs, dir, - ".markdownlint.cjs", + ".markdownlint.mjs", + [], noRequire, - importOrRequireConfig( - fs, - dir, - ".markdownlint.mjs", - noRequire, - noop - ) + noop ) ) ) ) - ); - tasks.push( - readConfigs(). - then((config) => { - dirInfo.markdownlintConfig = config; - }) + ) ); - } - return dirInfo; - }; + tasks.push( + readConfigs(). + then((config) => { + dirInfo.markdownlintConfig = config; + }) + ); + } + return dirInfo; +}; // Get base markdownlint-cli2 options object const getBaseOptions = async ( @@ -448,86 +466,97 @@ const getBaseOptions = async ( }; // Enumerate files from globs and build directory infos -const enumerateFiles = - // eslint-disable-next-line max-len - async (fs, baseDirSystem, baseDir, globPatterns, dirToDirInfo, noErrors, noRequire) => { - const tasks = []; - const globbyOptions = { - "absolute": true, - "cwd": baseDir, - "dot": true, - "expandDirectories": false, - fs - }; - if (noErrors) { - globbyOptions.suppressErrors = true; - } - // Special-case literal files - const literalFiles = []; - const filteredGlobPatterns = globPatterns.filter( - (globPattern) => { - if (globPattern.startsWith(":")) { - literalFiles.push( - posixPath(pathDefault.resolve(baseDirSystem, globPattern.slice(1))) - ); - return false; - } - return true; +const enumerateFiles = async ( + fs, + baseDirSystem, + baseDir, + globPatterns, + dirToDirInfo, + noErrors, + noRequire +) => { + const tasks = []; + const globbyOptions = { + "absolute": true, + "cwd": baseDir, + "dot": true, + "expandDirectories": false, + fs + }; + if (noErrors) { + globbyOptions.suppressErrors = true; + } + // Special-case literal files + const literalFiles = []; + const filteredGlobPatterns = globPatterns.filter( + (globPattern) => { + if (globPattern.startsWith(":")) { + literalFiles.push( + posixPath(pathDefault.resolve(baseDirSystem, globPattern.slice(1))) + ); + return false; } - ).map((globPattern) => globPattern.replace(/^\\:/u, ":")); - const baseMarkdownlintOptions = dirToDirInfo[baseDir].markdownlintOptions; - const globsForIgnore = - (baseMarkdownlintOptions.globs || []). - filter((glob) => glob.startsWith("!")); - const filteredLiteralFiles = - ((literalFiles.length > 0) && (globsForIgnore.length > 0)) - ? removeIgnoredFiles(baseDir, literalFiles, globsForIgnore) - : literalFiles; - // Manually expand directories to avoid globby call to dir-glob.sync - const expandedDirectories = await Promise.all( - filteredGlobPatterns.map((globPattern) => { - const barePattern = - globPattern.startsWith("!") - ? globPattern.slice(1) - : globPattern; - const globPath = ( - pathPosix.isAbsolute(barePattern) || - pathDefault.isAbsolute(barePattern) - ) - ? barePattern - : pathPosix.join(baseDir, barePattern); - return fs.promises.stat(globPath). - then((stats) => (stats.isDirectory() - ? pathPosix.join(globPattern, "**") - : globPattern)). - catch(() => globPattern); - }) - ); - // Process glob patterns - // eslint-disable-next-line no-inline-comments - const { globby } = await import(/* webpackMode: "eager" */ "globby"); - const files = [ - ...await globby(expandedDirectories, globbyOptions), - ...filteredLiteralFiles - ]; - for (const file of files) { - const dir = pathPosix.dirname(file); - const dirInfo = getAndProcessDirInfo( - fs, - tasks, - dirToDirInfo, - dir, - null, - noRequire, - false - ); - dirInfo.files.push(file); + return true; } - await Promise.all(tasks); - }; + ).map((globPattern) => globPattern.replace(/^\\:/u, ":")); + const baseMarkdownlintOptions = dirToDirInfo[baseDir].markdownlintOptions; + const globsForIgnore = + (baseMarkdownlintOptions.globs || []). + filter((glob) => glob.startsWith("!")); + const filteredLiteralFiles = + ((literalFiles.length > 0) && (globsForIgnore.length > 0)) + ? removeIgnoredFiles(baseDir, literalFiles, globsForIgnore) + : literalFiles; + // Manually expand directories to avoid globby call to dir-glob.sync + const expandedDirectories = await Promise.all( + filteredGlobPatterns.map((globPattern) => { + const barePattern = + globPattern.startsWith("!") + ? globPattern.slice(1) + : globPattern; + const globPath = ( + pathPosix.isAbsolute(barePattern) || + pathDefault.isAbsolute(barePattern) + ) + ? barePattern + : pathPosix.join(baseDir, barePattern); + return fs.promises.stat(globPath). + then((stats) => (stats.isDirectory() + ? pathPosix.join(globPattern, "**") + : globPattern)). + catch(() => globPattern); + }) + ); + // Process glob patterns + // eslint-disable-next-line no-inline-comments + const { globby } = await import(/* webpackMode: "eager" */ "globby"); + const files = [ + ...await globby(expandedDirectories, globbyOptions), + ...filteredLiteralFiles + ]; + for (const file of files) { + const dir = pathPosix.dirname(file); + const dirInfo = getAndProcessDirInfo( + fs, + tasks, + dirToDirInfo, + dir, + null, + noRequire, + false + ); + dirInfo.files.push(file); + } + await Promise.all(tasks); +}; // Enumerate (possibly missing) parent directories and update directory infos -const enumerateParents = async (fs, baseDir, dirToDirInfo, noRequire) => { +const enumerateParents = async ( + fs, + baseDir, + dirToDirInfo, + noRequire +) => { const tasks = []; // Create a lookup of baseDir and parents @@ -571,136 +600,144 @@ const enumerateParents = async (fs, baseDir, dirToDirInfo, noRequire) => { }; // Create directory info objects by enumerating file globs -const createDirInfos = - // eslint-disable-next-line max-len - async (fs, baseDirSystem, baseDir, globPatterns, dirToDirInfo, optionsOverride, noErrors, noRequire) => { - await enumerateFiles( - fs, - baseDirSystem, - baseDir, - globPatterns, - dirToDirInfo, - noErrors, - noRequire - ); - await enumerateParents( - fs, - baseDir, - dirToDirInfo, - noRequire - ); +const createDirInfos = async ( + fs, + baseDirSystem, + baseDir, + globPatterns, + modulePaths, + dirToDirInfo, + optionsOverride, + noErrors, + noRequire +) => { + await enumerateFiles( + fs, + baseDirSystem, + baseDir, + globPatterns, + dirToDirInfo, + noErrors, + noRequire + ); + await enumerateParents( + fs, + baseDir, + dirToDirInfo, + noRequire + ); - // Merge file lists with identical configuration - const dirs = Object.keys(dirToDirInfo); - dirs.sort((a, b) => b.length - a.length); - const dirInfos = []; - const noConfigDirInfo = - // eslint-disable-next-line unicorn/consistent-function-scoping - (dirInfo) => ( - dirInfo.parent && - !dirInfo.markdownlintConfig && - !dirInfo.markdownlintOptions - ); - const tasks = []; - for (const dir of dirs) { - const dirInfo = dirToDirInfo[dir]; - if (noConfigDirInfo(dirInfo)) { - if (dirInfo.parent) { - appendToArray(dirInfo.parent.files, dirInfo.files); - } - dirToDirInfo[dir] = null; - } else { - const { markdownlintOptions, relativeDir } = dirInfo; - if (markdownlintOptions && markdownlintOptions.customRules) { - tasks.push( - importOrRequireIds( - relativeDir || dir, - markdownlintOptions.customRules, - noRequire - ).then((customRules) => { - // Expand nested arrays (for packages that export multiple rules) - markdownlintOptions.customRules = customRules.flat(); - }) - ); - } - if (markdownlintOptions && markdownlintOptions.markdownItPlugins) { - tasks.push( - importOrRequireIdsAndParams( - relativeDir || dir, - markdownlintOptions.markdownItPlugins, - noRequire - ).then((markdownItPlugins) => { - markdownlintOptions.markdownItPlugins = markdownItPlugins; - }) - ); - } - dirInfos.push(dirInfo); + // Merge file lists with identical configuration + const dirs = Object.keys(dirToDirInfo); + dirs.sort((a, b) => b.length - a.length); + const dirInfos = []; + const noConfigDirInfo = + // eslint-disable-next-line unicorn/consistent-function-scoping + (dirInfo) => ( + dirInfo.parent && + !dirInfo.markdownlintConfig && + !dirInfo.markdownlintOptions + ); + const tasks = []; + for (const dir of dirs) { + const dirInfo = dirToDirInfo[dir]; + if (noConfigDirInfo(dirInfo)) { + if (dirInfo.parent) { + appendToArray(dirInfo.parent.files, dirInfo.files); } - } - await Promise.all(tasks); - for (const dirInfo of dirInfos) { - while (dirInfo.parent && !dirToDirInfo[dirInfo.parent.dir]) { - dirInfo.parent = dirInfo.parent.parent; + dirToDirInfo[dir] = null; + } else { + const { markdownlintOptions, relativeDir } = dirInfo; + if (markdownlintOptions && markdownlintOptions.customRules) { + tasks.push( + importOrRequireIds( + [ relativeDir || dir, ...modulePaths ], + markdownlintOptions.customRules, + noRequire + ).then((customRules) => { + // Expand nested arrays (for packages that export multiple rules) + markdownlintOptions.customRules = customRules.flat(); + }) + ); } + if (markdownlintOptions && markdownlintOptions.markdownItPlugins) { + tasks.push( + importOrRequireIdsAndParams( + [ relativeDir || dir, ...modulePaths ], + markdownlintOptions.markdownItPlugins, + noRequire + ).then((markdownItPlugins) => { + markdownlintOptions.markdownItPlugins = markdownItPlugins; + }) + ); + } + dirInfos.push(dirInfo); + } + } + await Promise.all(tasks); + for (const dirInfo of dirInfos) { + while (dirInfo.parent && !dirToDirInfo[dirInfo.parent.dir]) { + dirInfo.parent = dirInfo.parent.parent; } + } - // Verify dirInfos is simplified - // if ( - // dirInfos.filter( - // (di) => di.parent && !dirInfos.includes(di.parent) - // ).length > 0 - // ) { - // throw new Error("Extra parent"); - // } - // if ( - // dirInfos.filter( - // (di) => !di.parent && (di.dir !== baseDir) - // ).length > 0 - // ) { - // throw new Error("Missing parent"); - // } - // if ( - // dirInfos.filter( - // (di) => di.parent && - // !((di.markdownlintConfig ? 1 : 0) ^ (di.markdownlintOptions ? 1 : 0)) - // ).length > 0 - // ) { - // throw new Error("Missing object"); - // } - // if (dirInfos.filter((di) => di.dir === "/").length > 0) { - // throw new Error("Includes root"); - // } - - // Merge configuration by inheritance - for (const dirInfo of dirInfos) { - let markdownlintOptions = dirInfo.markdownlintOptions || {}; - let { markdownlintConfig } = dirInfo; - let parent = dirInfo; - // eslint-disable-next-line prefer-destructuring - while ((parent = parent.parent)) { - if (parent.markdownlintOptions) { - markdownlintOptions = mergeOptions( - parent.markdownlintOptions, - markdownlintOptions - ); - } - if ( - !markdownlintConfig && - parent.markdownlintConfig && - !markdownlintOptions.config - ) { - // eslint-disable-next-line prefer-destructuring - markdownlintConfig = parent.markdownlintConfig; - } + // Verify dirInfos is simplified + // if ( + // dirInfos.filter( + // (di) => di.parent && !dirInfos.includes(di.parent) + // ).length > 0 + // ) { + // throw new Error("Extra parent"); + // } + // if ( + // dirInfos.filter( + // (di) => !di.parent && (di.dir !== baseDir) + // ).length > 0 + // ) { + // throw new Error("Missing parent"); + // } + // if ( + // dirInfos.filter( + // (di) => di.parent && + // !((di.markdownlintConfig ? 1 : 0) ^ (di.markdownlintOptions ? 1 : 0)) + // ).length > 0 + // ) { + // throw new Error("Missing object"); + // } + // if (dirInfos.filter((di) => di.dir === "/").length > 0) { + // throw new Error("Includes root"); + // } + + // Merge configuration by inheritance + for (const dirInfo of dirInfos) { + let markdownlintOptions = dirInfo.markdownlintOptions || {}; + let { markdownlintConfig } = dirInfo; + let parent = dirInfo; + // eslint-disable-next-line prefer-destructuring + while ((parent = parent.parent)) { + if (parent.markdownlintOptions) { + markdownlintOptions = mergeOptions( + parent.markdownlintOptions, + markdownlintOptions + ); + } + if ( + !markdownlintConfig && + parent.markdownlintConfig && + !markdownlintOptions.config + ) { + // eslint-disable-next-line prefer-destructuring + markdownlintConfig = parent.markdownlintConfig; } - dirInfo.markdownlintOptions = mergeOptions( - markdownlintOptions, - optionsOverride - ); - dirInfo.markdownlintConfig = markdownlintConfig; } - return dirInfos; - }; + dirInfo.markdownlintOptions = mergeOptions( + markdownlintOptions, + optionsOverride + ); + dirInfo.markdownlintConfig = markdownlintConfig; + } + return dirInfos; +}; // Lint files in groups by shared configuration const lintFiles = async (fs, dirInfos, fileContents) => { @@ -820,6 +857,7 @@ const outputSummary = async ( relativeDir, summary, outputFormatters, + modulePaths, logMessage, logError ) => { @@ -832,8 +870,9 @@ const outputSummary = async ( logError }; const dir = relativeDir || baseDir; + const dirs = [ dir, ...modulePaths ]; const formattersAndParams = outputFormatters - ? await importOrRequireIdsAndParams(dir, outputFormatters) + ? await importOrRequireIdsAndParams(dirs, outputFormatters) : [ [ require("markdownlint-cli2-formatter-default") ] ]; await Promise.all(formattersAndParams.map((formatterAndParams) => { const [ formatter, ...formatterParams ] = formatterAndParams; @@ -937,12 +976,15 @@ const main = async (params) => { logMessage(`Finding: ${globPatterns.join(" ")}`); } // Create linting tasks + const modulePaths = (baseMarkdownlintOptions.modulePaths || []). + map((path) => pathDefault.resolve(baseDir, path)); const dirInfos = await createDirInfos( fs, baseDirSystem, baseDir, globPatterns, + modulePaths, dirToDirInfo, optionsOverride, noErrors, @@ -973,7 +1015,13 @@ const main = async (params) => { (optionsOverride && optionsOverride.outputFormatters) || baseMarkdownlintOptions.outputFormatters; const errorsPresent = await outputSummary( - baseDir, relativeDir, summary, outputFormatters, logMessage, logError + baseDir, + relativeDir, + summary, + outputFormatters, + modulePaths, + logMessage, + logError ); // Return result return errorsPresent ? 1 : 0; diff --git a/resolve-and-require.js b/resolve-and-require.js index 668157ac..3f169919 100644 --- a/resolve-and-require.js +++ b/resolve-and-require.js @@ -6,12 +6,12 @@ * Wrapper for calling Node's require.resolve/require with an additional path. * @param {Object} req Node's require implementation (or equivalent). * @param {String} id Package identifier to require. - * @param {String} dir Directory to include when resolving paths. + * @param {String[]} dirs Directories to include when resolving paths. * @returns {Object} Exported module content. */ -const resolveAndRequire = (req, id, dir) => { +const resolveAndRequire = (req, id, dirs) => { const resolvePaths = req.resolve.paths ? req.resolve.paths("") : []; - const paths = [ dir, ...resolvePaths ]; + const paths = [ ...dirs, ...resolvePaths ]; const resolved = req.resolve(id, { paths }); return req(resolved); }; diff --git a/schema/markdownlint-cli2-config-schema.json b/schema/markdownlint-cli2-config-schema.json index 48e7afb4..bd9e4109 100644 --- a/schema/markdownlint-cli2-config-schema.json +++ b/schema/markdownlint-cli2-config-schema.json @@ -74,6 +74,16 @@ "minItems": 1 } }, + "modulePaths": { + "description": "Additional paths to resolve module locations from (only valid at the root)", + "type": "array", + "default": [], + "items": { + "description": "Path to resolve module locations from", + "type": "string", + "minLength": 1 + } + }, "noInlineConfig": { "description": "Whether to disable support of HTML comments within Markdown content", "type": "boolean", diff --git a/test/markdownlint-cli2-test-cases.js b/test/markdownlint-cli2-test-cases.js index 5f1a093b..5bce13a5 100644 --- a/test/markdownlint-cli2-test-cases.js +++ b/test/markdownlint-cli2-test-cases.js @@ -1176,6 +1176,13 @@ const testCases = ({ "post": deleteDirectory }); + testCase({ + "name": "modulePaths", + "args": [ "**/*.md" ], + "exitCode": 1, + "usesRequire": true + }); + }; module.exports = testCases; diff --git a/test/markdownlint-cli2-test.js b/test/markdownlint-cli2-test.js index cfde663e..ec77b24e 100644 --- a/test/markdownlint-cli2-test.js +++ b/test/markdownlint-cli2-test.js @@ -111,7 +111,7 @@ test("validateMarkdownlintConfigSchema", async (t) => { }); test("validateMarkdownlintCli2ConfigSchema", async (t) => { - t.plan(84); + t.plan(85); // Validate schema const { addSchema, validate } = diff --git a/test/modulePaths/.markdownlint-cli2.jsonc b/test/modulePaths/.markdownlint-cli2.jsonc new file mode 100644 index 00000000..e93eb42f --- /dev/null +++ b/test/modulePaths/.markdownlint-cli2.jsonc @@ -0,0 +1,18 @@ +{ + "customRules": [ + "markdownlint-rule-sample-commonjs" + ], + "markdownItPlugins": [ + [ "custom-markdown-it-plugin" ] + ], + "outputFormatters": [ + [ "output-formatter-sample-commonjs" ] + ], + "modulePaths": [ + "../invalid", + "../customRules", + "../markdownItPlugins/module", + "../no-config", + "../outputFormatters-module" + ] +} diff --git a/test/modulePaths/dir/about.md b/test/modulePaths/dir/about.md new file mode 100644 index 00000000..56d31c78 --- /dev/null +++ b/test/modulePaths/dir/about.md @@ -0,0 +1,6 @@ +# About # + +Text text text +1. List +3. List +3. List diff --git a/test/modulePaths/dir/hr.md b/test/modulePaths/dir/hr.md new file mode 100644 index 00000000..7a141621 --- /dev/null +++ b/test/modulePaths/dir/hr.md @@ -0,0 +1,3 @@ +# hr + +--- diff --git a/test/modulePaths/dir/link.md b/test/modulePaths/dir/link.md new file mode 100644 index 00000000..88b7f3b6 --- /dev/null +++ b/test/modulePaths/dir/link.md @@ -0,0 +1,3 @@ +# Heading + +Text [ link ](https://example.com) diff --git a/test/modulePaths/dir/subdir/info.md b/test/modulePaths/dir/subdir/info.md new file mode 100644 index 00000000..fc9dba79 --- /dev/null +++ b/test/modulePaths/dir/subdir/info.md @@ -0,0 +1,3 @@ +## Information +Text ` code1` text `code2 ` text + diff --git a/test/modulePaths/viewme.md b/test/modulePaths/viewme.md new file mode 100644 index 00000000..d60ebf5a --- /dev/null +++ b/test/modulePaths/viewme.md @@ -0,0 +1,14 @@ +# Title + +> Tagline + + +# Description + +Text text text +Text text text +Text text text + +## Summary + +Text text text \ No newline at end of file diff --git a/test/resolve-and-require-test.js b/test/resolve-and-require-test.js index 96edff00..49038b38 100644 --- a/test/resolve-and-require-test.js +++ b/test/resolve-and-require-test.js @@ -12,7 +12,7 @@ test("built-in module", (t) => { t.plan(1); t.deepEqual( require("node:fs"), - resolveAndRequire(require, "fs", __dirname) + resolveAndRequire(require, "fs", [ __dirname ]) ); }); @@ -20,7 +20,7 @@ test("locally-installed module", (t) => { t.plan(1); t.deepEqual( require("markdownlint"), - resolveAndRequire(require, "markdownlint", __dirname) + resolveAndRequire(require, "markdownlint", [ __dirname ]) ); }); @@ -31,7 +31,7 @@ test("relative (to __dirname) path to module", (t) => { resolveAndRequire( require, "./customRules/node_modules/markdownlint-rule-sample-commonjs", - __dirname + [ __dirname ] ) ); }); @@ -48,7 +48,7 @@ test("module in alternate node_modules", (t) => { resolveAndRequire( require, "markdownlint-rule-sample-commonjs", - path.join(__dirname, "customRules") + [ path.join(__dirname, "customRules") ] ) ); }); @@ -67,7 +67,35 @@ test("module in alternate node_modules and no require.resolve.paths", (t) => { resolveAndRequire( require, "markdownlint-rule-sample-commonjs", - path.join(__dirname, "customRules") + [ path.join(__dirname, "customRules") ] + ) + ); +}); + +test("module local, relative, and in alternate node_modules", (t) => { + t.plan(3); + const dirs = [ + __dirname, + path.join(__dirname, "customRules") + ]; + t.deepEqual( + require("markdownlint"), + resolveAndRequire(require, "markdownlint", dirs) + ); + t.deepEqual( + require("./customRules/node_modules/markdownlint-rule-sample-commonjs"), + resolveAndRequire( + require, + "./customRules/node_modules/markdownlint-rule-sample-commonjs", + dirs + ) + ); + t.deepEqual( + require("./customRules/node_modules/markdownlint-rule-sample-commonjs"), + resolveAndRequire( + require, + "markdownlint-rule-sample-commonjs", + dirs ) ); }); diff --git a/test/snapshots/markdownlint-cli2-test-exec.js.md b/test/snapshots/markdownlint-cli2-test-exec.js.md index bc75c062..2e700e90 100644 --- a/test/snapshots/markdownlint-cli2-test-exec.js.md +++ b/test/snapshots/markdownlint-cli2-test-exec.js.md @@ -7190,3 +7190,35 @@ Generated by [AVA](https://avajs.dev). Summary: 2 error(s)␊ `, } + +## modulePaths (exec) + +> Snapshot 1 + + { + exitCode: 1, + formatterCodeQuality: '', + formatterJson: '', + formatterJunit: '', + formatterSarif: '', + stderr: `cjs: dir/about.md 1 MD021/no-multiple-space-closed-atx␊ + cjs: dir/about.md 4 MD032/blanks-around-lists␊ + cjs: dir/about.md 5 MD029/ol-prefix␊ + cjs: dir/hr.md 3 sample-rule-commonjs␊ + cjs: dir/subdir/info.md 1 MD022/blanks-around-headings/blanks-around-headers␊ + cjs: dir/subdir/info.md 1 MD041/first-line-heading/first-line-h1␊ + cjs: dir/subdir/info.md 2 MD038/no-space-in-code␊ + cjs: dir/subdir/info.md 2 MD038/no-space-in-code␊ + cjs: dir/subdir/info.md 4 MD012/no-multiple-blanks␊ + cjs: viewme.md 3 MD009/no-trailing-spaces␊ + cjs: viewme.md 5 MD012/no-multiple-blanks␊ + cjs: viewme.md 6 MD025/single-title/single-h1␊ + cjs: viewme.md 12 MD019/no-multiple-space-atx␊ + cjs: viewme.md 14 MD047/single-trailing-newline␊ + `, + stdout: `markdownlint-cli2 vX.Y.Z (markdownlint vX.Y.Z)␊ + Finding: **/*.md␊ + Linting: 5 file(s)␊ + Summary: 14 error(s)␊ + `, + } diff --git a/test/snapshots/markdownlint-cli2-test-exec.js.snap b/test/snapshots/markdownlint-cli2-test-exec.js.snap index ca23f486..dae4f494 100644 Binary files a/test/snapshots/markdownlint-cli2-test-exec.js.snap and b/test/snapshots/markdownlint-cli2-test-exec.js.snap differ diff --git a/test/snapshots/markdownlint-cli2-test-main.js.md b/test/snapshots/markdownlint-cli2-test-main.js.md index 0b103a89..b5acede4 100644 --- a/test/snapshots/markdownlint-cli2-test-main.js.md +++ b/test/snapshots/markdownlint-cli2-test-main.js.md @@ -5265,3 +5265,35 @@ Generated by [AVA](https://avajs.dev). Summary: 2 error(s)␊ `, } + +## modulePaths (main) + +> Snapshot 1 + + { + exitCode: 1, + formatterCodeQuality: '', + formatterJson: '', + formatterJunit: '', + formatterSarif: '', + stderr: `cjs: dir/about.md 1 MD021/no-multiple-space-closed-atx␊ + cjs: dir/about.md 4 MD032/blanks-around-lists␊ + cjs: dir/about.md 5 MD029/ol-prefix␊ + cjs: dir/hr.md 3 sample-rule-commonjs␊ + cjs: dir/subdir/info.md 1 MD022/blanks-around-headings/blanks-around-headers␊ + cjs: dir/subdir/info.md 1 MD041/first-line-heading/first-line-h1␊ + cjs: dir/subdir/info.md 2 MD038/no-space-in-code␊ + cjs: dir/subdir/info.md 2 MD038/no-space-in-code␊ + cjs: dir/subdir/info.md 4 MD012/no-multiple-blanks␊ + cjs: viewme.md 3 MD009/no-trailing-spaces␊ + cjs: viewme.md 5 MD012/no-multiple-blanks␊ + cjs: viewme.md 6 MD025/single-title/single-h1␊ + cjs: viewme.md 12 MD019/no-multiple-space-atx␊ + cjs: viewme.md 14 MD047/single-trailing-newline␊ + `, + stdout: `markdownlint-cli2 vX.Y.Z (markdownlint vX.Y.Z)␊ + Finding: **/*.md␊ + Linting: 5 file(s)␊ + Summary: 14 error(s)␊ + `, + } diff --git a/test/snapshots/markdownlint-cli2-test-main.js.snap b/test/snapshots/markdownlint-cli2-test-main.js.snap index 60adceb8..1aead3bc 100644 Binary files a/test/snapshots/markdownlint-cli2-test-main.js.snap and b/test/snapshots/markdownlint-cli2-test-main.js.snap differ