mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-21 21:30:47 +02:00
Add support for "markdownlint-configure-file" inline comments (fixes #264).
This commit is contained in:
parent
dd66a33d75
commit
6ce426cf88
7 changed files with 183 additions and 56 deletions
27
README.md
27
README.md
|
@ -196,6 +196,33 @@ the following syntax is supported:
|
|||
|
||||
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
|
||||
|
||||
### Linting
|
||||
|
|
|
@ -17,7 +17,7 @@ module.exports.frontMatterRe =
|
|||
// Regular expression for matching inline disable/enable comments
|
||||
const inlineCommentRe =
|
||||
// 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;
|
||||
|
||||
// Regular expressions for range matching
|
||||
|
|
|
@ -300,72 +300,105 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
|
|||
* @param {string[]} lines List of content lines.
|
||||
* @param {string[]} frontMatterLines List of front matter lines.
|
||||
* @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
|
||||
* names.
|
||||
* @returns {Object.<string, RuleConfiguration>[]} Enabled rules for each line.
|
||||
* @returns {Object} Effective configuration and enabled rules per line number.
|
||||
*/
|
||||
function getEnabledRulesPerLineNumber(
|
||||
ruleList,
|
||||
lines,
|
||||
frontMatterLines,
|
||||
noInlineConfig,
|
||||
effectiveConfig,
|
||||
config,
|
||||
aliasToRuleNames) {
|
||||
// Shared variables
|
||||
let enabledRules = {};
|
||||
let capturedRules = {};
|
||||
const allRuleNames = [];
|
||||
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
|
||||
// Helper functions
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function handleInlineConfig(perLine, forEachMatch, forEachLine) {
|
||||
const input = perLine ? lines : [ lines.join("\n") ];
|
||||
input.forEach((line) => {
|
||||
if (!noInlineConfig) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (forEachLine) {
|
||||
forEachLine();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 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 items = parameter ?
|
||||
parameter.trim().toUpperCase().split(/\s+/) :
|
||||
allRuleNames;
|
||||
items.forEach((nameUpper) => {
|
||||
(aliasToRuleNames[nameUpper] || []).forEach((ruleName) => {
|
||||
enabledRules[ruleName] = enabled;
|
||||
});
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function enableDisableFile(action, parameter) {
|
||||
if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) {
|
||||
applyEnableDisable(action, parameter);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function captureRestoreEnableDisable(action, parameter) {
|
||||
if (action === "CAPTURE") {
|
||||
capturedRules = { ...enabledRules };
|
||||
} else if (action === "RESTORE") {
|
||||
enabledRules = { ...capturedRules };
|
||||
} else if ((action === "ENABLE") || (action === "DISABLE")) {
|
||||
enabledRules = { ...enabledRules };
|
||||
applyEnableDisable(action, parameter);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function updateLineState() {
|
||||
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];
|
||||
});
|
||||
let capturedRules = enabledRules;
|
||||
// eslint-disable-next-line jsdoc/require-jsdoc
|
||||
function forMatch(match, byLine) {
|
||||
const action = match[1].toUpperCase();
|
||||
if (action === "CAPTURE") {
|
||||
if (byLine) {
|
||||
capturedRules = { ...enabledRules };
|
||||
}
|
||||
} else if (action === "RESTORE") {
|
||||
if (byLine) {
|
||||
enabledRules = { ...capturedRules };
|
||||
}
|
||||
} else {
|
||||
// action in [ENABLE, DISABLE, ENABLE-FILE, DISABLE-FILE]
|
||||
const isfile = action.endsWith("-FILE");
|
||||
if ((byLine && !isfile) || (!byLine && isfile)) {
|
||||
const enabled = (action.startsWith("ENABLE"));
|
||||
const items = match[2] ?
|
||||
match[2].trim().toUpperCase().split(/\s+/) :
|
||||
allRuleNames;
|
||||
items.forEach((nameUpper) => {
|
||||
(aliasToRuleNames[nameUpper] || []).forEach((ruleName) => {
|
||||
enabledRules[ruleName] = enabled;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
|
||||
[ false, true ].forEach((byLine) => {
|
||||
lines.forEach((line) => {
|
||||
if (!noInlineConfig) {
|
||||
let match = helpers.inlineCommentRe.exec(line);
|
||||
if (match) {
|
||||
enabledRules = { ...enabledRules };
|
||||
while (match) {
|
||||
forMatch(match, byLine);
|
||||
match = helpers.inlineCommentRe.exec(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (byLine) {
|
||||
enabledRulesPerLineNumber.push(enabledRules);
|
||||
}
|
||||
});
|
||||
});
|
||||
return enabledRulesPerLineNumber;
|
||||
capturedRules = enabledRules;
|
||||
handleInlineConfig(true, enableDisableFile);
|
||||
handleInlineConfig(true, captureRestoreEnableDisable, updateLineState);
|
||||
// Return results
|
||||
return {
|
||||
effectiveConfig,
|
||||
enabledRulesPerLineNumber
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -438,11 +471,15 @@ function lintContent(
|
|||
const lines = content.split(helpers.newLineRe);
|
||||
annotateTokens(tokens, lines);
|
||||
const aliasToRuleNames = mapAliasToRuleNames(ruleList);
|
||||
const effectiveConfig =
|
||||
getEffectiveConfig(ruleList, config, aliasToRuleNames);
|
||||
const enabledRulesPerLineNumber = getEnabledRulesPerLineNumber(
|
||||
ruleList, lines, frontMatterLines, noInlineConfig,
|
||||
effectiveConfig, aliasToRuleNames);
|
||||
const { effectiveConfig, enabledRulesPerLineNumber } =
|
||||
getEnabledRulesPerLineNumber(
|
||||
ruleList,
|
||||
lines,
|
||||
frontMatterLines,
|
||||
noInlineConfig,
|
||||
config,
|
||||
aliasToRuleNames
|
||||
);
|
||||
// Create parameters for rules
|
||||
const params = {
|
||||
name,
|
||||
|
|
5
test/inline-configure-file-invalid.md
Normal file
5
test/inline-configure-file-invalid.md
Normal 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 } } -->
|
33
test/inline-configure-file-multiple-instances.md
Normal file
33
test/inline-configure-file-multiple-instances.md
Normal 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:
|
20
test/inline-configure-file-multiple-lines.md
Normal file
20
test/inline-configure-file-multiple-lines.md
Normal 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:
|
5
test/inline-configure-file-single-line.md
Normal file
5
test/inline-configure-file-single-line.md
Normal 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 } } -->
|
Loading…
Add table
Add a link
Reference in a new issue