Add support for "markdownlint-configure-file" inline comments (fixes #264).

This commit is contained in:
David Anson 2020-04-05 19:47:12 -07:00
parent dd66a33d75
commit 6ce426cf88
7 changed files with 183 additions and 56 deletions

View file

@ -196,6 +196,33 @@ the following syntax is supported:
This can be used to "hide" `markdownlint` comments at the bottom of a file. This can be used to "hide" `markdownlint` comments at the bottom of a file.
In cases where it is desirable to change the configuration of one or more rules
for a file, the following more advanced syntax is supported:
* Confiure: `<!-- markdownlint-configure-file { options.config JSON } -->`
For example:
```markdown
<!-- markdownlint-configure-file { "MD013": { "line_length": 70 } } -->
```
or
```markdown
<!-- markdownlint-configure-file
{
"hr-style": {
"style": "---"
},
"no-trailing-spaces": false
}
-->
```
These changes apply to the entire file regardless of where the comment is
located. Multiple such comments (if present) are applied top-to-bottom.
## API ## API
### Linting ### Linting

View file

@ -17,7 +17,7 @@ module.exports.frontMatterRe =
// Regular expression for matching inline disable/enable comments // Regular expression for matching inline disable/enable comments
const inlineCommentRe = const inlineCommentRe =
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file)((?:\s+[a-z0-9_-]+)*)\s*-->/ig; /<!--\s*markdownlint-(?:(?:(disable|enable|capture|restore|disable-file|enable-file)((?:\s+[a-z0-9_-]+)*))|(?:(configure-file)\s+([\s\S]*?)))\s*-->/ig;
module.exports.inlineCommentRe = inlineCommentRe; module.exports.inlineCommentRe = inlineCommentRe;
// Regular expressions for range matching // Regular expressions for range matching

View file

@ -300,44 +300,60 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
* @param {string[]} lines List of content lines. * @param {string[]} lines List of content lines.
* @param {string[]} frontMatterLines List of front matter lines. * @param {string[]} frontMatterLines List of front matter lines.
* @param {boolean} noInlineConfig Whether to allow inline configuration. * @param {boolean} noInlineConfig Whether to allow inline configuration.
* @param {Configuration} effectiveConfig Effective configuration. * @param {Configuration} config Configuration object.
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule * @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
* names. * names.
* @returns {Object.<string, RuleConfiguration>[]} Enabled rules for each line. * @returns {Object} Effective configuration and enabled rules per line number.
*/ */
function getEnabledRulesPerLineNumber( function getEnabledRulesPerLineNumber(
ruleList, ruleList,
lines, lines,
frontMatterLines, frontMatterLines,
noInlineConfig, noInlineConfig,
effectiveConfig, config,
aliasToRuleNames) { aliasToRuleNames) {
// Shared variables
let enabledRules = {}; let enabledRules = {};
let capturedRules = {};
const allRuleNames = []; const allRuleNames = [];
ruleList.forEach((rule) => { const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
const ruleName = rule.names[0].toUpperCase(); // Helper functions
allRuleNames.push(ruleName);
enabledRules[ruleName] = !!effectiveConfig[ruleName];
});
let capturedRules = enabledRules;
// eslint-disable-next-line jsdoc/require-jsdoc // eslint-disable-next-line jsdoc/require-jsdoc
function forMatch(match, byLine) { function handleInlineConfig(perLine, forEachMatch, forEachLine) {
const action = match[1].toUpperCase(); const input = perLine ? lines : [ lines.join("\n") ];
if (action === "CAPTURE") { input.forEach((line) => {
if (byLine) { if (!noInlineConfig) {
capturedRules = { ...enabledRules }; let match = null;
while ((match = helpers.inlineCommentRe.exec(line))) {
const action = (match[1] || match[3]).toUpperCase();
const parameter = match[2] || match[4];
forEachMatch(action, parameter);
} }
} else if (action === "RESTORE") {
if (byLine) {
enabledRules = { ...capturedRules };
} }
} else { if (forEachLine) {
// action in [ENABLE, DISABLE, ENABLE-FILE, DISABLE-FILE] forEachLine();
const isfile = action.endsWith("-FILE"); }
if ((byLine && !isfile) || (!byLine && isfile)) { });
}
// eslint-disable-next-line jsdoc/require-jsdoc
function configureFile(action, parameter) {
if (action === "CONFIGURE-FILE") {
try {
const json = JSON.parse(parameter);
config = {
...config,
...json
};
} catch (ex) {
// Ignore parse errors for inline configuration
}
}
}
// eslint-disable-next-line jsdoc/require-jsdoc
function applyEnableDisable(action, parameter) {
const enabled = (action.startsWith("ENABLE")); const enabled = (action.startsWith("ENABLE"));
const items = match[2] ? const items = parameter ?
match[2].trim().toUpperCase().split(/\s+/) : parameter.trim().toUpperCase().split(/\s+/) :
allRuleNames; allRuleNames;
items.forEach((nameUpper) => { items.forEach((nameUpper) => {
(aliasToRuleNames[nameUpper] || []).forEach((ruleName) => { (aliasToRuleNames[nameUpper] || []).forEach((ruleName) => {
@ -345,27 +361,44 @@ function getEnabledRulesPerLineNumber(
}); });
}); });
} }
// eslint-disable-next-line jsdoc/require-jsdoc
function enableDisableFile(action, parameter) {
if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
applyEnableDisable(action, parameter);
} }
} }
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); // eslint-disable-next-line jsdoc/require-jsdoc
[ false, true ].forEach((byLine) => { function captureRestoreEnableDisable(action, parameter) {
lines.forEach((line) => { if (action === "CAPTURE") {
if (!noInlineConfig) { capturedRules = { ...enabledRules };
let match = helpers.inlineCommentRe.exec(line); } else if (action === "RESTORE") {
if (match) { enabledRules = { ...capturedRules };
} else if ((action === "ENABLE") || (action === "DISABLE")) {
enabledRules = { ...enabledRules }; enabledRules = { ...enabledRules };
while (match) { applyEnableDisable(action, parameter);
forMatch(match, byLine);
match = helpers.inlineCommentRe.exec(line);
} }
} }
} // eslint-disable-next-line jsdoc/require-jsdoc
if (byLine) { function updateLineState() {
enabledRulesPerLineNumber.push(enabledRules); enabledRulesPerLineNumber.push(enabledRules);
} }
// Handle inline comments
handleInlineConfig(false, configureFile);
const effectiveConfig = getEffectiveConfig(
ruleList, config, aliasToRuleNames);
ruleList.forEach((rule) => {
const ruleName = rule.names[0].toUpperCase();
allRuleNames.push(ruleName);
enabledRules[ruleName] = !!effectiveConfig[ruleName];
}); });
}); capturedRules = enabledRules;
return enabledRulesPerLineNumber; handleInlineConfig(true, enableDisableFile);
handleInlineConfig(true, captureRestoreEnableDisable, updateLineState);
// Return results
return {
effectiveConfig,
enabledRulesPerLineNumber
};
} }
/** /**
@ -438,11 +471,15 @@ function lintContent(
const lines = content.split(helpers.newLineRe); const lines = content.split(helpers.newLineRe);
annotateTokens(tokens, lines); annotateTokens(tokens, lines);
const aliasToRuleNames = mapAliasToRuleNames(ruleList); const aliasToRuleNames = mapAliasToRuleNames(ruleList);
const effectiveConfig = const { effectiveConfig, enabledRulesPerLineNumber } =
getEffectiveConfig(ruleList, config, aliasToRuleNames); getEnabledRulesPerLineNumber(
const enabledRulesPerLineNumber = getEnabledRulesPerLineNumber( ruleList,
ruleList, lines, frontMatterLines, noInlineConfig, lines,
effectiveConfig, aliasToRuleNames); frontMatterLines,
noInlineConfig,
config,
aliasToRuleNames
);
// Create parameters for rules // Create parameters for rules
const params = { const params = {
name, name,

View file

@ -0,0 +1,5 @@
# Inline Configure File Invalid
Not normally too long of a line, but it would have been from an inline config.
<!-- markdownlint-configure-file { "MD013": { line_length: 70 } } -->

View file

@ -0,0 +1,33 @@
# Inline Configure File Multiple Instances
***
{MD035:3}
Trailing spaces:
<!-- markdownlint-configure-file
{
"hr-style": {
"style": "***"
},
"no-trailing-spaces": false
}
-->
***
{MD035:17}
Trailing spaces:
<!-- markdownlint-configure-file
{
"hr-style": {
"style": "---"
}
}
-->
***
{MD035:30}
Trailing spaces:

View file

@ -0,0 +1,20 @@
# Inline Configure File Multiple Lines
***
{MD035:3}
Trailing spaces:
<!-- markdownlint-configure-file
{
"hr-style": {
"style": "---"
},
"no-trailing-spaces": false
}
-->
***
{MD035:17}
Trailing spaces:

View file

@ -0,0 +1,5 @@
# Inline Configure File Single Line
Not normally too long of a line, but it is here from an inline config. {MD013}
<!-- markdownlint-configure-file { "MD013": { "line_length": 70 } } -->