Add "configParsers" option so custom parsers can be used to handle the content of markdownlint-configure-file inline comments (fixes #528).

This commit is contained in:
David Anson 2022-06-05 22:32:22 -07:00
parent bbec8c5c1e
commit 00082ee8a5
6 changed files with 267 additions and 108 deletions

View file

@ -1560,6 +1560,39 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
});
return effectiveConfig;
}
/**
* Parse the content of a configuration file.
*
* @param {string} name Name of the configuration file.
* @param {string} content Configuration content.
* @param {ConfigurationParser[]} parsers Parsing function(s).
* @returns {Object} Configuration object and error message.
*/
function parseConfiguration(name, content, parsers) {
let config = null;
let message = "";
const errors = [];
let index = 0;
// Try each parser
(parsers || [JSON.parse]).every((parser) => {
try {
config = parser(content);
}
catch (error) {
errors.push(`Parser ${index++}: ${error.message}`);
}
return !config;
});
// Message if unable to parse
if (!config) {
errors.unshift(`Unable to parse '${name}'`);
message = errors.join("; ");
}
return {
config,
message
};
}
/**
* Create a mapping of enabled rules per line.
*
@ -1568,11 +1601,12 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
* @param {string[]} frontMatterLines List of front matter lines.
* @param {boolean} noInlineConfig Whether to allow inline configuration.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[]} configParsers Configuration parsers.
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
* names.
* @returns {Object} Effective configuration and enabled rules per line number.
*/
function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlineConfig, config, aliasToRuleNames) {
function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlineConfig, config, configParsers, aliasToRuleNames) {
// Shared variables
let enabledRules = {};
let capturedRules = {};
@ -1603,12 +1637,9 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin
// eslint-disable-next-line jsdoc/require-jsdoc
function configureFile(action, parameter) {
if (action === "CONFIGURE-FILE") {
try {
const json = JSON.parse(parameter);
config = Object.assign(Object.assign({}, config), json);
}
catch (_a) {
// Ignore parse errors for inline configuration
const { "config": parsed } = parseConfiguration("CONFIGURE-FILE", parameter, configParsers);
if (parsed) {
config = Object.assign(Object.assign({}, config), parsed);
}
}
}
@ -1683,6 +1714,7 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin
* @param {string} content Markdown content.
* @param {Object} md Instance of markdown-it.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[]} configParsers Configuration parsers.
* @param {RegExp} frontMatter Regular expression for front matter.
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
* @param {boolean} noInlineConfig Whether to allow inline configuration.
@ -1690,7 +1722,7 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin
* @param {Function} callback Callback (err, result) function.
* @returns {void}
*/
function lintContent(ruleList, name, content, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, callback) {
function lintContent(ruleList, name, content, md, config, configParsers, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, callback) {
// Remove UTF-8 byte order marker (if present)
content = content.replace(/^\uFEFF/, "");
// Remove front matter
@ -1698,7 +1730,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul
const { frontMatterLines } = removeFrontMatterResult;
content = removeFrontMatterResult.content;
// Get enabled rules per line (with HTML comments present)
const { effectiveConfig, enabledRulesPerLineNumber } = getEnabledRulesPerLineNumber(ruleList, content.split(helpers.newLineRe), frontMatterLines, noInlineConfig, config, mapAliasToRuleNames(ruleList));
const { effectiveConfig, enabledRulesPerLineNumber } = getEnabledRulesPerLineNumber(ruleList, content.split(helpers.newLineRe), frontMatterLines, noInlineConfig, config, configParsers, mapAliasToRuleNames(ruleList));
// Hide the content of HTML comments from rules, etc.
content = helpers.clearHtmlCommentText(content);
// Parse content into tokens and lines
@ -1924,6 +1956,7 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul
* @param {string} file Path of file to lint.
* @param {Object} md Instance of markdown-it.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[]} configParsers Configuration parsers.
* @param {RegExp} frontMatter Regular expression for front matter.
* @param {boolean} handleRuleFailures Whether to handle exceptions in rules.
* @param {boolean} noInlineConfig Whether to allow inline configuration.
@ -1933,13 +1966,13 @@ function lintContent(ruleList, name, content, md, config, frontMatter, handleRul
* @param {Function} callback Callback (err, result) function.
* @returns {void}
*/
function lintFile(ruleList, file, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, callback) {
function lintFile(ruleList, file, md, config, configParsers, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, callback) {
// eslint-disable-next-line jsdoc/require-jsdoc
function lintContentWrapper(err, content) {
if (err) {
return callback(err);
}
return lintContent(ruleList, file, content, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, callback);
return lintContent(ruleList, file, content, md, config, configParsers, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, callback);
}
// Make a/synchronous call to read file
if (synchronous) {
@ -1977,6 +2010,7 @@ function lintInput(options, synchronous, callback) {
const strings = options.strings || {};
const stringsKeys = Object.keys(strings);
const config = options.config || { "default": true };
const configParsers = options.configParsers || null;
const frontMatter = (options.frontMatter === undefined) ?
helpers.frontMatterRe : options.frontMatter;
const handleRuleFailures = !!options.handleRuleFailures;
@ -2016,13 +2050,13 @@ function lintInput(options, synchronous, callback) {
// Lint next file
concurrency++;
currentItem = files.shift();
lintFile(ruleList, currentItem, md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, lintWorkerCallback);
lintFile(ruleList, currentItem, md, config, configParsers, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, fs, synchronous, lintWorkerCallback);
}
else if (stringsKeys.length > 0) {
// Lint next string
concurrency++;
currentItem = stringsKeys.shift();
lintContent(ruleList, currentItem, strings[currentItem] || "", md, config, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, lintWorkerCallback);
lintContent(ruleList, currentItem, strings[currentItem] || "", md, config, configParsers, frontMatter, handleRuleFailures, noInlineConfig, resultVersion, lintWorkerCallback);
}
else if (concurrency === 0) {
// Finish
@ -2087,39 +2121,6 @@ function markdownlintSync(options) {
});
return results;
}
/**
* Parse the content of a configuration file.
*
* @param {string} name Name of the configuration file.
* @param {string} content Configuration content.
* @param {ConfigurationParser[]} parsers Parsing function(s).
* @returns {Object} Configuration object and error message.
*/
function parseConfiguration(name, content, parsers) {
let config = null;
let message = "";
const errors = [];
let index = 0;
// Try each parser
(parsers || [JSON.parse]).every((parser) => {
try {
config = parser(content);
}
catch (error) {
errors.push(`Parser ${index++}: ${error.message}`);
}
return !config;
});
// Message if unable to parse
if (!config) {
errors.unshift(`Unable to parse '${name}'`);
message = errors.join("; ");
}
return {
config,
message
};
}
/**
* Resolve referenced "extends" path in a configuration file
* using path.resolve() with require.resolve() as a fallback.