mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-22 05:40:48 +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.
|
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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