Add "enabled" and "severity"/"warning" to rule in Configuration object, add corresponding documentation and tests, update demo web app (fixes #254)”.

This commit is contained in:
David Anson 2025-09-22 21:39:09 -07:00
parent c6f248321e
commit 607be34b5d
20 changed files with 3792 additions and 867 deletions

View file

@ -362,54 +362,111 @@ of `files` or `strings` should be set to provide input.
##### options.config
Type: `Object` mapping `String` to `Boolean | "error" | Object`
Type: `Object` mapping `String` to `Boolean | "error" | "warning" | Object`
Configures the rules to use.
Object keys are rule names/aliases; object values are the rule's configuration.
The value `false` disables a rule. The values `true` or `"error"` enable a rule
in its default configuration and report violations as errors. Passing an object
enables and customizes the rule. The special `default` rule assigns the default
for all rules. Using a tag name (e.g., `whitespace`) and a setting of `false`,
`true`, or `"error"` applies that setting to all rules with that tag. When no
configuration object is passed or the optional `default` setting is not present,
all rules are enabled.
in its default configuration and report violations as errors. The value
`"warning"` enables a rule in its default configuration and reports violations
as warnings. Passing an object enables *and* customizes the rule; the properties
`severity` (`"error" | "warning"`) and `enabled` (`false | true`) can be used in
this context. The special `default` rule assigns the default for all rules.
Using a tag name (e.g., `whitespace`) and a setting of `false`, `true`,
`"error"`, or `"warning"` applies that setting to all rules with that tag. When
no configuration object is passed or the optional `default` setting is not
present, all rules are enabled.
The following syntax disables the specified rule, tag, or `default`:
```javascript
{
"rule_name": false
"rule_tag_or_default": false
}
```
The following syntax enables the specified rule, tag, or `default`:
The following syntax enables the specified rule, tag, or `default` to report
violations as errors:
```javascript
{
"rule_name": true
"rule_tag_or_default": true
// OR
"rule_name": "error"
"rule_tag_or_default": "error"
}
```
The following syntax enables and configures the specified rule:
The following syntax enables the specified rule, tag, or `default` to report
violations as warnings:
```javascript
{
"rule_name": {
"parameter": "value"
}
// OR
"rule_name": {
"parameter": "value",
"rule_tag_or_default": "warning"
}
```
The following syntax enables and configures the specified rule to report
violations as errors:
```javascript
{
"rule": {
"severity": "error"
}
// OR
"rule": {
"rule_parameter": "value"
}
// OR
"rule": {
"severity": "error",
"rule_parameter": "value"
}
}
```
> Note that `error` and `severity` are not supported by library versions earlier
> than `0.39.0`. However, all examples above behave the same.
The following syntax enables and configures the specified rule to report
violations as warnings:
```javascript
{
"rule": {
"severity": "warning"
}
// OR
"rule": {
"severity": "warning",
"rule_parameter": "value"
}
}
```
> Note that values `"error"` and `"warning"` and the property `severity` are not
> supported by library versions earlier than `0.39.0`. However, the examples
> above behave the same there, with warnings being reported as errors.
The following syntax disables and configures the specified rule:
```javascript
{
"rule": {
"enabled": false,
"rule_parameter": "value"
}
// OR
"rule": {
"enabled": false,
"severity": "warning",
"rule_parameter": "value"
}
}
```
> Note that this example behaves **differently** with library versions earlier
> than `0.39.0` because the property `enabled` is not supported: it **enables**
> the rule instead of **disabling** it. As such, this syntax is discouraged when
> interoperability is important.
To evaluate a configuration object, the `default` setting is applied first, then
keys are processed in order from top to bottom. If multiple values apply to a

View file

@ -47,6 +47,9 @@ textarea {
font-style: italic;
white-space: pre-wrap;
}
.warning {
background: rgb(255, 255, 0, 0.5);
}
.error {
background: rgb(255, 0, 0, 0.3);
}
@ -67,7 +70,7 @@ textarea {
min-height: 0;
}
.highlight {
background: rgb(255, 0, 0, 0.8);
background: rgb(0, 255, 255, 0.8);
}
.inset {
box-sizing: border-box;

View file

@ -86,8 +86,8 @@
return `[Unsupported renderer "${renderer}"]`;
}
// Highlight error ranges
function highlightErrors(results, className) {
// Highlight ranges
function highlightRanges(results, className) {
for (const result of results) {
const { errorRange, lineNumber } = result;
const line = document.getElementById(`l${lineNumber}`);
@ -150,14 +150,18 @@
sanitize(result.errorContext) +
"\"</span>]" :
"") +
" [<span class='detail'>" +
result.severity +
"</span>]" +
(result.fixInfo ?
" [<a href='#fix' target='" +
resultJson +
"' class='detail'>Fix</a>]" :
"");
}).join("<br/>");
// Highlight errors
highlightErrors(allLintErrors, "error");
// Highlight errors and warnings
highlightRanges(allLintErrors.filter((error) => error.severity === "warning"), "warning");
highlightRanges(allLintErrors.filter((error) => error.severity === "error"), "error");
}
// Load from a string or File object
@ -216,7 +220,7 @@
for (const element of [ ...document.getElementsByClassName("highlight") ]) {
element.classList.remove("highlight");
}
highlightErrors([ resultJson ], "highlight");
highlightRanges([ resultJson ], "highlight");
var line = document.getElementById(`l${resultJson.lineNumber}`);
line.scrollIntoView();
e.preventDefault();

View file

@ -64,7 +64,7 @@ for (const rule of rules) {
const ruleProperties = Object.fromEntries(
Object.entries(
ruleData.oneOf.at(-1).properties
).filter(([ key ]) => key !== "severity")
).filter(([ key ]) => ((key !== "enabled") && (key !== "severity")))
);
if (Object.keys(ruleProperties).length > 0) {
section.push(

File diff suppressed because it is too large Load diff

View file

@ -82,6 +82,23 @@ export function applyFixes(input: string, errors: LintError[]): string;
* @returns {string} SemVer string.
*/
export function getVersion(): string;
/**
* Result object for getEffectiveConfig.
*/
export type GetEffectiveConfigResult = {
/**
* Effective configuration.
*/
effectiveConfig: Configuration;
/**
* Rules enabled.
*/
rulesEnabled: Map<string, boolean>;
/**
* Rules severity.
*/
rulesSeverity: Map<string, "error" | "warning">;
};
/**
* Result object for getEnabledRulesPerLineNumber.
*/
@ -93,11 +110,15 @@ export type EnabledRulesPerLineNumberResult = {
/**
* Enabled rules per line number.
*/
enabledRulesPerLineNumber: any[];
enabledRulesPerLineNumber: Map<string, boolean>[];
/**
* Enabled rule list.
*/
enabledRuleList: Rule[];
/**
* Rules severity.
*/
rulesSeverity: Map<string, "error" | "warning">;
};
/**
* Function to implement rule logic.

View file

@ -236,44 +236,87 @@ function mapAliasToRuleNames(ruleList) {
return aliasToRuleNames;
}
/**
* Result object for getEffectiveConfig.
*
* @typedef {Object} GetEffectiveConfigResult
* @property {Configuration} effectiveConfig Effective configuration.
* @property {Map<string, boolean>} rulesEnabled Rules enabled.
* @property {Map<string, "error" | "warning">} rulesSeverity Rules severity.
*/
/**
* Apply (and normalize) configuration object.
*
* @param {Rule[]} ruleList List of rules.
* @param {Configuration} config Configuration object.
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
* names.
* @returns {Configuration} Effective configuration.
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
* @returns {GetEffectiveConfigResult} Effective configuration and rule severities.
*/
function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
const defaultKey = Object.keys(config).filter(
(key) => key.toUpperCase() === "DEFAULT"
);
const ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]];
let ruleDefaultEnable = true;
/** @type {"error" | "warning"} */
let ruleDefaultSeverity = "error";
Object.entries(config).every(([ key, value ]) => {
if (key.toUpperCase() === "DEFAULT") {
ruleDefaultEnable = !!value;
if (value === "warning") {
ruleDefaultSeverity = "warning";
}
return false;
}
return true;
});
/** @type {Configuration} */
const effectiveConfig = {};
for (const rule of ruleList) {
const ruleName = rule.names[0].toUpperCase();
effectiveConfig[ruleName] = ruleDefault;
/** @type {Map<string, boolean>} */
const rulesEnabled = new Map();
/** @type {Map<string, "error" | "warning">} */
const rulesSeverity = new Map();
const emptyObject = Object.freeze({});
for (const ruleName of ruleList.map((rule) => rule.names[0].toUpperCase())) {
effectiveConfig[ruleName] = emptyObject;
rulesEnabled.set(ruleName, ruleDefaultEnable);
rulesSeverity.set(ruleName, ruleDefaultSeverity);
}
// for (const ruleName of deprecatedRuleNames) {
// effectiveConfig[ruleName] = false;
// }
for (const key of Object.keys(config)) {
let value = config[key];
if (value) {
value = (value instanceof Object) ?
Object.fromEntries(Object.entries(value).filter(([ k ]) => k !== "severity")) :
{};
} else {
value = false;
}
for (const [ key, value ] of Object.entries(config)) {
const keyUpper = key.toUpperCase();
/** @type {boolean} */
let enabled = false;
/** @type {"error" | "warning"} */
let severity = "error";
let effectiveValue = {};
if (value) {
if (value instanceof Object) {
/** @type {{ enabled?: boolean, severity?: "error" | "warning" }} */
const valueObject = value;
enabled = (valueObject.enabled === undefined) ? true : !!valueObject.enabled;
severity = (valueObject.severity === "warning") ? "warning" : "error";
effectiveValue = Object.fromEntries(
Object.entries(value).filter(
([ k ]) => (k !== "enabled") && (k !== "severity")
)
);
} else {
enabled = true;
severity = (value === "warning") ? "warning" : "error";
}
}
for (const ruleName of (aliasToRuleNames[keyUpper] || [])) {
effectiveConfig[ruleName] = value;
Object.freeze(effectiveValue);
effectiveConfig[ruleName] = effectiveValue;
rulesEnabled.set(ruleName, enabled);
rulesSeverity.set(ruleName, severity);
}
}
return effectiveConfig;
return {
effectiveConfig,
rulesEnabled,
rulesSeverity
};
}
/**
@ -281,8 +324,9 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
*
* @typedef {Object} EnabledRulesPerLineNumberResult
* @property {Configuration} effectiveConfig Effective configuration.
* @property {any[]} enabledRulesPerLineNumber Enabled rules per line number.
* @property {Map<string, boolean>[]} enabledRulesPerLineNumber Enabled rules per line number.
* @property {Rule[]} enabledRuleList Enabled rule list.
* @property {Map<string, "error" | "warning">} rulesSeverity Rules severity.
*/
/**
@ -294,8 +338,7 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) {
* @param {boolean} noInlineConfig Whether to allow inline configuration.
* @param {Configuration} config Configuration object.
* @param {ConfigurationParser[] | undefined} configParsers Configuration parsers.
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule
* names.
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule names.
* @returns {EnabledRulesPerLineNumberResult} Effective configuration and enabled rules per line number.
*/
function getEnabledRulesPerLineNumber(
@ -307,9 +350,10 @@ function getEnabledRulesPerLineNumber(
configParsers,
aliasToRuleNames) {
// Shared variables
let enabledRules = {};
let capturedRules = {};
const allRuleNames = [];
/** @type {Map<string, boolean>} */
let enabledRules = new Map();
/** @type {Map<string, boolean>} */
let capturedRules = enabledRules;
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length);
// Helper functions
// eslint-disable-next-line jsdoc/require-jsdoc
@ -349,13 +393,14 @@ function getEnabledRulesPerLineNumber(
}
// eslint-disable-next-line jsdoc/require-jsdoc
function applyEnableDisable(action, parameter, state) {
state = { ...state };
state = new Map(state);
const enabled = (action.startsWith("ENABLE"));
const trimmed = parameter && parameter.trim();
// eslint-disable-next-line no-use-before-define
const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames;
for (const nameUpper of items) {
for (const ruleName of (aliasToRuleNames[nameUpper] || [])) {
state[ruleName] = enabled;
state.set(ruleName, enabled);
}
}
return state;
@ -397,29 +442,24 @@ function getEnabledRulesPerLineNumber(
}
// Handle inline comments
handleInlineConfig([ lines.join("\n") ], configureFile);
const effectiveConfig = getEffectiveConfig(
ruleList, config, aliasToRuleNames);
for (const rule of ruleList) {
const ruleName = rule.names[0].toUpperCase();
allRuleNames.push(ruleName);
enabledRules[ruleName] = !!effectiveConfig[ruleName];
}
const { effectiveConfig, rulesEnabled, rulesSeverity } = getEffectiveConfig(ruleList, config, aliasToRuleNames);
const allRuleNames = [ ...rulesEnabled.keys() ];
enabledRules = new Map(rulesEnabled);
capturedRules = enabledRules;
handleInlineConfig(lines, enableDisableFile);
handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState);
handleInlineConfig(lines, disableLineNextLine);
// Create the list of rules that are used at least once
const enabledRuleList = [];
for (const [ index, ruleName ] of allRuleNames.entries()) {
if (enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine[ruleName])) {
enabledRuleList.push(ruleList[index]);
}
}
const enabledRuleList = ruleList.filter((rule) => {
const ruleName = rule.names[0].toUpperCase();
return enabledRulesPerLineNumber.some((enabledRulesForLine) => enabledRulesForLine.get(ruleName));
});
// Return results
return {
effectiveConfig,
enabledRulesPerLineNumber,
enabledRuleList
enabledRuleList,
rulesSeverity
};
}
@ -464,7 +504,7 @@ function lintContent(
const { frontMatterLines } = removeFrontMatterResult;
content = removeFrontMatterResult.content;
// Get enabled rules per line (with HTML comments present)
const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList } =
const { effectiveConfig, enabledRulesPerLineNumber, enabledRuleList, rulesSeverity } =
getEnabledRulesPerLineNumber(
ruleList,
content.split(helpers.newLineRe),
@ -541,12 +581,12 @@ function lintContent(
} else if (rule.parser === "micromark") {
parsers = parsersMicromark;
}
const params = {
const params = Object.freeze({
...paramsBase,
...tokens,
parsers,
"config": (typeof effectiveConfig[ruleName] === "object") ? { ...effectiveConfig[ruleName] } : effectiveConfig[ruleName]
};
"config": effectiveConfig[ruleName]
});
// eslint-disable-next-line jsdoc/require-jsdoc
function throwError(property) {
throw new Error(
@ -561,7 +601,7 @@ function lintContent(
throwError("lineNumber");
}
const lineNumber = errorInfo.lineNumber + frontMatterLines.length;
if (!enabledRulesPerLineNumber[lineNumber][ruleName]) {
if (!enabledRulesPerLineNumber[lineNumber].get(ruleName)) {
return;
}
if (errorInfo.detail &&
@ -638,7 +678,8 @@ function lintContent(
"errorContext": errorInfo.context?.replace(helpers.newLineRe, " ") || null,
"errorRange": errorInfo.range ? [ ...errorInfo.range ] : null,
"fixInfo": fixInfo ? cleanFixInfo : null,
"severity": "error"
// @ts-ignore
"severity": rulesSeverity.get(ruleName)
});
}
// Call (possibly external) rule function to report errors

View file

@ -16,7 +16,7 @@ for (const rule in configSchema.properties) {
const subproperties = Object.fromEntries(
Object.entries(
properties.oneOf?.at(-1).properties || []
).filter(([ key ]) => key !== "severity")
).filter(([ key ]) => ((key !== "enabled") && (key !== "severity")))
);
if (Object.keys(subproperties).length > 0) {
/** @type {Object<string, any>} */

View file

@ -30,7 +30,7 @@ const schema = {
"description": "Default state for all rules",
"oneOf": [
{ "type": "boolean" },
{ "enum": [ "error" ] }
{ "enum": [ "error", "warning" ] }
],
"default": true
},
@ -66,7 +66,7 @@ for (const rule of rules) {
`${rule.names.join("/")} : ${rule.description} : ${rule.information}`,
"oneOf": [
{ "type": "boolean" },
{ "enum": [ "error" ] }
{ "enum": [ "error", "warning" ] }
],
"default": true
};
@ -74,12 +74,15 @@ for (const rule of rules) {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"description": "Whether to enable the rule",
"type": "boolean",
"default": true
},
"severity": {
"description": "Rule severity",
"type": "string",
"enum": [
"error"
],
"enum": [ "error", "warning" ],
"default": "error"
}
}
@ -656,7 +659,7 @@ for (const [ tag, tagTags ] of Object.entries(tags)) {
"description": `${tag} : ${tagTags.join(", ")}`,
"oneOf": [
{ "type": "boolean" },
{ "enum": [ "error" ] }
{ "enum": [ "error", "warning" ] }
],
"default": true
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
# Configure File Off Enable Inline
+ List item
Text (text)[.]
<!-- markdownlint-disable -->
+ List item
Text (text)[.]
<!-- markdownlint-restore -->
+ List item
Text (text)[.]
<!-- markdownlint-enable MD004 MD011 -->
+ List item {MD004}
Text (text)[.] {MD011}
<!-- markdownlint-disable -->
+ List item
Text (text)[.]
<!-- markdownlint-enable MD004 MD011 -->
+ List item {MD004}
Text (text)[.] {MD011}
<!-- markdownlint-configure-file {
"MD004": {
"enabled": false,
"severity": "warning",
"style": "dash"
},
"MD011": {
"enabled": false,
"severity": "error"
}
} -->

View file

@ -0,0 +1,58 @@
# Configure File With Severity Alias
Text * text* {MD037}
Text ` text` {MD038}
Text [ text](.)
+ List item {MD004}
Text (text)[.] {MD011}
2. List item
<!-- markdownlint-disable -->
Text * text*
Text ` text`
Text [ text](.)
+ List item
Text (text)[.]
2. List item
<!-- markdownlint-restore -->
Text * text* {MD037}
Text ` text` {MD038}
Text [ text](.)
+ List item {MD004}
Text (text)[.] {MD011}
2. List item
<!-- markdownlint-configure-file {
"default": false,
"no-space-in-emphasis": "error",
"no-space-in-code": "warning",
"no-space-in-links": false,
"ul-style": {
"severity": "error",
"style": "dash"
},
"no-reversed-links": {
"severity": "warning"
},
"ol-prefix": {
"enabled": false
}
} -->

View file

@ -1,16 +1,16 @@
# Configure File With Severity
# Configure File With Severity Name
Text * text* {MD037}
Text ` text` {MD038}
Text [ text](.) {MD039}
Text [ text](.)
+ List item {MD004}
Text (text)[.] {MD011}
2. List item {MD029}
2. List item
<!-- markdownlint-disable -->
@ -32,27 +32,27 @@ Text * text* {MD037}
Text ` text` {MD038}
Text [ text](.) {MD039}
Text [ text](.)
+ List item {MD004}
Text (text)[.] {MD011}
2. List item {MD029}
2. List item
<!-- markdownlint-configure-file {
"default": false,
"MD037": "error",
"MD038": "error",
"MD039": "error",
"MD038": "warning",
"MD039": false,
"MD004": {
"severity": "error",
"style": "dash"
},
"MD011": {
"severity": "error"
"severity": "warning"
},
"MD029": {
"severity": "error"
"enabled": false
}
} -->

View file

@ -0,0 +1,50 @@
# Configure File With Severity Tag
Text * text* {MD037}
Text ` text` {MD038}
Text [ text](.) {MD039}
+ List item
Text (text)[.] {MD011}
2. List item {MD029}
<!-- markdownlint-disable -->
Text * text*
Text ` text`
Text [ text](.)
+ List item
Text (text)[.]
2. List item
<!-- markdownlint-restore -->
Text * text* {MD037}
Text ` text` {MD038}
Text [ text](.) {MD039}
+ List item
Text (text)[.] {MD011}
2. List item {MD029}
<!-- markdownlint-configure-file {
"default": false,
"emphasis": "error",
"code": "warning",
"bullet": false,
"links": "warning",
"ol": "error"
} -->

View file

@ -2037,7 +2037,7 @@ test("customRulesParamsAreFrozen", (t) => {
const pending = [ params ];
let current = null;
while ((current = pending.shift())) {
t.true(Object.isFrozen(current) || (current === params));
t.true(Object.isFrozen(current));
for (const name of Object.getOwnPropertyNames(current)) {
// @ts-ignore
const value = current[name];
@ -2083,7 +2083,7 @@ test("customRulesParamsAreFrozen", (t) => {
});
test("customRulesParamsAreStable", (t) => {
t.plan(6);
t.plan(4);
const config1 = { "value1": 10 };
const config2 = { "value2": 20 };
/** @type {import("markdownlint").Options} */
@ -2108,7 +2108,6 @@ test("customRulesParamsAreStable", (t) => {
t.deepEqual(config, config1, `Unexpected config in sync path: ${config}.`);
return Promise.resolve().then(() => {
t.deepEqual(config, config1, `Unexpected config in async path: ${config}.`);
config.extra = 1;
});
}
},
@ -2124,7 +2123,6 @@ test("customRulesParamsAreStable", (t) => {
t.deepEqual(config, config2, `Unexpected config in sync path: ${config}.`);
return Promise.resolve().then(() => {
t.deepEqual(config, config2, `Unexpected config in async path: ${config}.`);
config.extra = 2;
});
}
}
@ -2133,10 +2131,7 @@ test("customRulesParamsAreStable", (t) => {
"string": "# Heading"
}
};
return lintPromise(options).then(() => {
t.deepEqual(config1, { "value1": 10 });
t.deepEqual(config2, { "value2": 20 });
});
return lintPromise(options);
});
test("customRulesParamsAreExpected", (t) => {
@ -2155,7 +2150,7 @@ test("customRulesParamsAreExpected", (t) => {
"description": "description",
"tags": [ "tag" ],
"parser": "none",
"function": (params) => t.deepEqual(params.config, true)
"function": (params) => t.deepEqual(params.config, {})
},
{
"names": [ "name2" ],

View file

@ -347,6 +347,7 @@ test("getPreferredLineEnding", (t) => {
const actual = helpers.getPreferredLineEnding(input);
t.is(actual, expected, "Incorrect line ending returned.");
}
// @ts-ignore
t.is(helpers.getPreferredLineEnding("", null), "\n");
t.is(helpers.getPreferredLineEnding("", { "EOL": "\n" }), "\n");
t.is(helpers.getPreferredLineEnding("", { "EOL": "\r\n" }), "\r\n");
@ -358,29 +359,37 @@ test("expandTildePath", (t) => {
const homedir = os.homedir();
t.is(helpers.expandTildePath("", os), "");
t.is(helpers.expandTildePath("", {}), "");
// @ts-ignore
t.is(helpers.expandTildePath("", null), "");
t.is(
path.resolve(helpers.expandTildePath("~", os)),
homedir
);
// @ts-ignore
t.is(helpers.expandTildePath("~", null), "~");
t.is(helpers.expandTildePath("file", os), "file");
// @ts-ignore
t.is(helpers.expandTildePath("file", null), "file");
t.is(helpers.expandTildePath("/file", os), "/file");
// @ts-ignore
t.is(helpers.expandTildePath("/file", null), "/file");
t.is(
path.resolve(helpers.expandTildePath("~/file", os)),
path.join(homedir, "/file")
);
// @ts-ignore
t.is(helpers.expandTildePath("~/file", null), "~/file");
t.is(helpers.expandTildePath("dir/file", os), "dir/file");
// @ts-ignore
t.is(helpers.expandTildePath("dir/file", null), "dir/file");
t.is(helpers.expandTildePath("/dir/file", os), "/dir/file");
// @ts-ignore
t.is(helpers.expandTildePath("/dir/file", null), "/dir/file");
t.is(
path.resolve(helpers.expandTildePath("~/dir/file", os)),
path.join(homedir, "/dir/file")
);
// @ts-ignore
t.is(helpers.expandTildePath("~/dir/file", null), "~/dir/file");
});
@ -396,6 +405,7 @@ test("getReferenceLinkImageData().shortcuts", (t) => {
"parser": "none",
"function":
() => {
// @ts-ignore
const { shortcuts } = getReferenceLinkImageData();
t.is(shortcuts.size, 0, [ ...shortcuts.keys() ].join(", "));
}
@ -533,14 +543,14 @@ test("hasOverlap", (t) => {
test("formatLintResults", async(t) => {
t.plan(2);
t.deepEqual(formatLintResults(undefined), []);
const lintResults = await lint({ "strings": { "content": "# Heading\n<br/>" } });
const lintResults = await lint({ "strings": { "content": "# Heading\n<br/><!-- markdownlint-configure-file { \"MD033\": \"warning\" } -->" } });
t.deepEqual(
formatLintResults(lintResults),
[
"content:1:3 error MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: \"# Heading\"]",
"content:1 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: \"# Heading\"]",
"content:2:1 error MD033/no-inline-html Inline HTML [Element: br]",
"content:2:5 error MD047/single-trailing-newline Files should end with a single newline character"
"content:2:1 warning MD033/no-inline-html Inline HTML [Element: br]",
"content:2:64 error MD047/single-trailing-newline Files should end with a single newline character"
]
);
});

View file

@ -190,6 +190,16 @@ function getConfigTestImplementation(config, expected) {
};
}
/**
* Converts a config test scenario from errors to warnings.
*
* @param {Object.<string, string[]>} configTest Config test scenario.
* @returns {Object.<string, string[]>} Converted scenario.
*/
function configTestAsWarnings(configTest) {
return JSON.parse(JSON.stringify(configTest).replaceAll("error", "warning"));
}
const configTestExpected = {
"./test/atx_heading_spacing.md": [],
"./test/first_heading_bad_atx.md": []
@ -273,13 +283,23 @@ test("defaultError", getConfigTestImplementation(
test("defaultWarning", getConfigTestImplementation(
// @ts-ignore
{ "default": "warning" },
configTestAsWarnings(configTestExpected13511)
));
test("defaultMultipleTrue", getConfigTestImplementation(
{
"default": true,
"DEFAULT": false
},
configTestExpected13511
));
test("defaultOff", getConfigTestImplementation(
// @ts-ignore
{ "default": "off" },
configTestExpected13511
test("defaultMultipleFalse", getConfigTestImplementation(
{
"DEFAULT": false,
"default": true
},
configTestExpected
));
test("disableRules", getConfigTestImplementation(
@ -336,7 +356,7 @@ test("enableRulesTruthy", getConfigTestImplementation(
configTestExpected3511
));
test("enableRulesString", getConfigTestImplementation(
test("enableRulesError", getConfigTestImplementation(
{
"MD041": "error",
"default": false,
@ -346,6 +366,16 @@ test("enableRulesString", getConfigTestImplementation(
configTestExpected3511
));
test("enableRulesWarning", getConfigTestImplementation(
{
"MD041": "warning",
"default": false,
"no-multiple-space-atx": "warning",
"extra": "warning"
},
configTestAsWarnings(configTestExpected3511)
));
test("enableRulesObjectEmpty", getConfigTestImplementation(
{
"MD041": {},
@ -356,7 +386,7 @@ test("enableRulesObjectEmpty", getConfigTestImplementation(
configTestExpected3511
));
test("enableRulesObjectTruthy", getConfigTestImplementation(
test("enableRulesObjectSeverityTruthy", getConfigTestImplementation(
{
"MD041": {
// @ts-ignore
@ -374,7 +404,7 @@ test("enableRulesObjectTruthy", getConfigTestImplementation(
configTestExpected3511
));
test("enableRulesObjectFalsy", getConfigTestImplementation(
test("enableRulesObjectSeverityFalsy", getConfigTestImplementation(
{
"MD041": {
// @ts-ignore
@ -392,7 +422,7 @@ test("enableRulesObjectFalsy", getConfigTestImplementation(
configTestExpected3511
));
test("enableRulesObjectError", getConfigTestImplementation(
test("enableRulesObjectSeverityError", getConfigTestImplementation(
{
"MD041": {
"severity": "error"
@ -408,42 +438,90 @@ test("enableRulesObjectError", getConfigTestImplementation(
configTestExpected3511
));
test("enableRulesObjectWarning", getConfigTestImplementation(
test("enableRulesObjectSeverityWarning", getConfigTestImplementation(
{
"MD041": {
// @ts-ignore
"severity": "warning"
},
"default": false,
"no-multiple-space-atx": {
// @ts-ignore
"severity": "warning"
},
"extra": {
"severity": "warning"
}
},
configTestAsWarnings(configTestExpected3511)
));
test("enableRulesObjectEnabledTrue", getConfigTestImplementation(
{
"MD041": {
"enabled": true
},
"default": false,
"no-multiple-space-atx": {
"enabled": true
},
"extra": {
"enabled": true
}
},
configTestExpected3511
));
test("enableRulesObjectOff", getConfigTestImplementation(
test("enableRulesObjectEnabledFalse", getConfigTestImplementation(
{
"MD041": {
"enabled": false
},
"default": true,
"no-multiple-space-atx": {
"enabled": false
},
"extra": {
"enabled": false
}
},
configTestExpected1
));
test("enableRulesObjectEnabledTruthy", getConfigTestImplementation(
{
"MD041": {
// @ts-ignore
"severity": "off"
"enabled": 1
},
"default": false,
"no-multiple-space-atx": {
// @ts-ignore
"severity": "off"
"enabled": 1
},
"extra": {
"severity": "off"
"enabled": 1
}
},
configTestExpected3511
));
test("enableRulesObjectEnabledFalsy", getConfigTestImplementation(
{
"MD041": {
// @ts-ignore
"enabled": 0
},
"default": true,
"no-multiple-space-atx": {
// @ts-ignore
"enabled": 0
},
"extra": {
"enabled": 0
}
},
configTestExpected1
));
test("disableTag", getConfigTestImplementation(
{
"default": true,
@ -491,7 +569,7 @@ test("enableTagTruthy", getConfigTestImplementation(
configTestExpected135
));
test("enableTagString", getConfigTestImplementation(
test("enableTagError", getConfigTestImplementation(
{
"default": false,
"spaces": "error",
@ -500,6 +578,15 @@ test("enableTagString", getConfigTestImplementation(
configTestExpected135
));
test("enableTagWarning", getConfigTestImplementation(
{
"default": false,
"spaces": "warning",
"extra": "warning"
},
configTestAsWarnings(configTestExpected135)
));
test("styleFiles", async(t) => {
t.plan(8);
const files = await fs.promises.readdir("./style");
@ -1008,7 +1095,7 @@ test("readme", async(t) => {
});
test("validateJsonUsingConfigSchemaStrict", async(t) => {
t.plan(212);
t.plan(215);
// @ts-ignore
const ajv = new Ajv(ajvOptions);
const validateSchemaStrict = ajv.compile(configSchemaStrict);

View file

@ -11203,7 +11203,148 @@ Generated by [AVA](https://avajs.dev).
`,
}
## configure-file-with-severity.md
## configure-file-off-enable-inline.md
> Snapshot 1
{
errors: [
{
errorContext: null,
errorDetail: 'Expected: dash; Actual: plus',
errorRange: [
1,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 1,
insertText: '-',
},
lineNumber: 21,
ruleDescription: 'Unordered list style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md004.md',
ruleNames: [
'MD004',
'ul-style',
],
severity: 'warning',
},
{
errorContext: null,
errorDetail: 'Expected: dash; Actual: plus',
errorRange: [
1,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 1,
insertText: '-',
},
lineNumber: 33,
ruleDescription: 'Unordered list style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md004.md',
ruleNames: [
'MD004',
'ul-style',
],
severity: 'warning',
},
{
errorContext: null,
errorDetail: '(text)[.]',
errorRange: [
6,
9,
],
fixInfo: {
deleteCount: 9,
editColumn: 6,
insertText: '[text](.)',
},
lineNumber: 23,
ruleDescription: 'Reversed link syntax',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md011.md',
ruleNames: [
'MD011',
'no-reversed-links',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: '(text)[.]',
errorRange: [
6,
9,
],
fixInfo: {
deleteCount: 9,
editColumn: 6,
insertText: '[text](.)',
},
lineNumber: 35,
ruleDescription: 'Reversed link syntax',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md011.md',
ruleNames: [
'MD011',
'no-reversed-links',
],
severity: 'error',
},
],
fixed: `# Configure File Off Enable Inline␊
+ List item␊
Text (text)[.]␊
<!-- markdownlint-disable -->
+ List item␊
Text (text)[.]␊
<!-- markdownlint-restore -->
+ List item␊
Text (text)[.]␊
<!-- markdownlint-enable MD004 MD011 -->
- List item {MD004}␊
Text [text](.) {MD011}␊
<!-- markdownlint-disable -->
+ List item␊
Text (text)[.]␊
<!-- markdownlint-enable MD004 MD011 -->
- List item {MD004}␊
Text [text](.) {MD011}␊
<!-- markdownlint-configure-file {␊
"MD004": {␊
"enabled": false,␊
"severity": "warning",␊
"style": "dash"␊
},␊
"MD011": {␊
"enabled": false,␊
"severity": "error"␊
}␊
} -->␊
`,
}
## configure-file-with-severity-alias.md
> Snapshot 1
@ -11270,7 +11411,7 @@ Generated by [AVA](https://avajs.dev).
'MD011',
'no-reversed-links',
],
severity: 'error',
severity: 'warning',
},
{
errorContext: null,
@ -11291,8 +11432,430 @@ Generated by [AVA](https://avajs.dev).
'MD011',
'no-reversed-links',
],
severity: 'warning',
},
{
errorContext: '* t',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 3,
ruleDescription: 'Spaces inside emphasis markers',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md037.md',
ruleNames: [
'MD037',
'no-space-in-emphasis',
],
severity: 'error',
},
{
errorContext: '* t',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 31,
ruleDescription: 'Spaces inside emphasis markers',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md037.md',
ruleNames: [
'MD037',
'no-space-in-emphasis',
],
severity: 'error',
},
{
errorContext: '` text`',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 5,
ruleDescription: 'Spaces inside code span elements',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md038.md',
ruleNames: [
'MD038',
'no-space-in-code',
],
severity: 'warning',
},
{
errorContext: '` text`',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 33,
ruleDescription: 'Spaces inside code span elements',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md038.md',
ruleNames: [
'MD038',
'no-space-in-code',
],
severity: 'warning',
},
],
fixed: `# Configure File With Severity Alias␊
Text *text* {MD037}␊
Text \`text\` {MD038}␊
Text [ text](.)␊
- List item {MD004}␊
Text [text](.) {MD011}␊
2. List item␊
<!-- markdownlint-disable -->
Text * text*
Text \` text\`␊
Text [ text](.)␊
+ List item␊
Text (text)[.]␊
2. List item␊
<!-- markdownlint-restore -->
Text *text* {MD037}␊
Text \`text\` {MD038}␊
Text [ text](.)␊
- List item {MD004}␊
Text [text](.) {MD011}␊
2. List item␊
<!-- markdownlint-configure-file {␊
"default": false,␊
"no-space-in-emphasis": "error",␊
"no-space-in-code": "warning",␊
"no-space-in-links": false,␊
"ul-style": {␊
"severity": "error",␊
"style": "dash"␊
},␊
"no-reversed-links": {␊
"severity": "warning"␊
},␊
"ol-prefix": {␊
"enabled": false␊
}␊
} -->␊
`,
}
## configure-file-with-severity-name.md
> Snapshot 1
{
errors: [
{
errorContext: null,
errorDetail: 'Expected: dash; Actual: plus',
errorRange: [
1,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 1,
insertText: '-',
},
lineNumber: 9,
ruleDescription: 'Unordered list style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md004.md',
ruleNames: [
'MD004',
'ul-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: 'Expected: dash; Actual: plus',
errorRange: [
1,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 1,
insertText: '-',
},
lineNumber: 37,
ruleDescription: 'Unordered list style',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md004.md',
ruleNames: [
'MD004',
'ul-style',
],
severity: 'error',
},
{
errorContext: null,
errorDetail: '(text)[.]',
errorRange: [
6,
9,
],
fixInfo: {
deleteCount: 9,
editColumn: 6,
insertText: '[text](.)',
},
lineNumber: 11,
ruleDescription: 'Reversed link syntax',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md011.md',
ruleNames: [
'MD011',
'no-reversed-links',
],
severity: 'warning',
},
{
errorContext: null,
errorDetail: '(text)[.]',
errorRange: [
6,
9,
],
fixInfo: {
deleteCount: 9,
editColumn: 6,
insertText: '[text](.)',
},
lineNumber: 39,
ruleDescription: 'Reversed link syntax',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md011.md',
ruleNames: [
'MD011',
'no-reversed-links',
],
severity: 'warning',
},
{
errorContext: '* t',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 3,
ruleDescription: 'Spaces inside emphasis markers',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md037.md',
ruleNames: [
'MD037',
'no-space-in-emphasis',
],
severity: 'error',
},
{
errorContext: '* t',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 31,
ruleDescription: 'Spaces inside emphasis markers',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md037.md',
ruleNames: [
'MD037',
'no-space-in-emphasis',
],
severity: 'error',
},
{
errorContext: '` text`',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 5,
ruleDescription: 'Spaces inside code span elements',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md038.md',
ruleNames: [
'MD038',
'no-space-in-code',
],
severity: 'warning',
},
{
errorContext: '` text`',
errorDetail: null,
errorRange: [
7,
1,
],
fixInfo: {
deleteCount: 1,
editColumn: 7,
},
lineNumber: 33,
ruleDescription: 'Spaces inside code span elements',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md038.md',
ruleNames: [
'MD038',
'no-space-in-code',
],
severity: 'warning',
},
],
fixed: `# Configure File With Severity Name␊
Text *text* {MD037}␊
Text \`text\` {MD038}␊
Text [ text](.)␊
- List item {MD004}␊
Text [text](.) {MD011}␊
2. List item␊
<!-- markdownlint-disable -->
Text * text*
Text \` text\`␊
Text [ text](.)␊
+ List item␊
Text (text)[.]␊
2. List item␊
<!-- markdownlint-restore -->
Text *text* {MD037}␊
Text \`text\` {MD038}␊
Text [ text](.)␊
- List item {MD004}␊
Text [text](.) {MD011}␊
2. List item␊
<!-- markdownlint-configure-file {␊
"default": false,␊
"MD037": "error",␊
"MD038": "warning",␊
"MD039": false,␊
"MD004": {␊
"severity": "error",␊
"style": "dash"␊
},␊
"MD011": {␊
"severity": "warning"␊
},␊
"MD029": {␊
"enabled": false␊
}␊
} -->␊
`,
}
## configure-file-with-severity-tag.md
> Snapshot 1
{
errors: [
{
errorContext: null,
errorDetail: '(text)[.]',
errorRange: [
6,
9,
],
fixInfo: {
deleteCount: 9,
editColumn: 6,
insertText: '[text](.)',
},
lineNumber: 11,
ruleDescription: 'Reversed link syntax',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md011.md',
ruleNames: [
'MD011',
'no-reversed-links',
],
severity: 'warning',
},
{
errorContext: null,
errorDetail: '(text)[.]',
errorRange: [
6,
9,
],
fixInfo: {
deleteCount: 9,
editColumn: 6,
insertText: '[text](.)',
},
lineNumber: 39,
ruleDescription: 'Reversed link syntax',
ruleInformation: 'https://github.com/DavidAnson/markdownlint/blob/v0.0.0/doc/md011.md',
ruleNames: [
'MD011',
'no-reversed-links',
],
severity: 'warning',
},
{
errorContext: null,
errorDetail: 'Expected: 1; Actual: 2; Style: 1/1/1',
@ -11393,7 +11956,7 @@ Generated by [AVA](https://avajs.dev).
'MD038',
'no-space-in-code',
],
severity: 'error',
severity: 'warning',
},
{
errorContext: '` text`',
@ -11413,7 +11976,7 @@ Generated by [AVA](https://avajs.dev).
'MD038',
'no-space-in-code',
],
severity: 'error',
severity: 'warning',
},
{
errorContext: '[ text]',
@ -11433,7 +11996,7 @@ Generated by [AVA](https://avajs.dev).
'MD039',
'no-space-in-links',
],
severity: 'error',
severity: 'warning',
},
{
errorContext: '[ text]',
@ -11453,10 +12016,10 @@ Generated by [AVA](https://avajs.dev).
'MD039',
'no-space-in-links',
],
severity: 'error',
severity: 'warning',
},
],
fixed: `# Configure File With Severity␊
fixed: `# Configure File With Severity Tag
Text *text* {MD037}␊
@ -11464,7 +12027,7 @@ Generated by [AVA](https://avajs.dev).
Text [text](.) {MD039}␊
- List item {MD004}
+ List item
Text [text](.) {MD011}␊
@ -11492,7 +12055,7 @@ Generated by [AVA](https://avajs.dev).
Text [text](.) {MD039}␊
- List item {MD004}
+ List item
Text [text](.) {MD011}␊
@ -11500,19 +12063,11 @@ Generated by [AVA](https://avajs.dev).
<!-- markdownlint-configure-file {␊
"default": false,␊
"MD037": "error",␊
"MD038": "error",␊
"MD039": "error",␊
"MD004": {␊
"severity": "error",␊
"style": "dash"␊
},␊
"MD011": {␊
"severity": "error"␊
},␊
"MD029": {␊
"severity": "error"␊
}␊
"emphasis": "error",␊
"code": "warning",␊
"bullet": false,␊
"links": "warning",␊
"ol": "error"␊
} -->␊
`,
}