mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-09-22 05:40:48 +02:00
Add options.handleRuleFailures for custom rule exceptions.
This commit is contained in:
parent
0e5c44617f
commit
0f72bf054b
4 changed files with 232 additions and 67 deletions
|
@ -21,7 +21,7 @@
|
||||||
"linebreak-style": "off",
|
"linebreak-style": "off",
|
||||||
"max-lines": "off",
|
"max-lines": "off",
|
||||||
"max-lines-per-function": "off",
|
"max-lines-per-function": "off",
|
||||||
"max-params": ["error", 9],
|
"max-params": ["error", 10],
|
||||||
"max-statements": ["error", 33],
|
"max-statements": ["error", 33],
|
||||||
"multiline-comment-style": ["error", "separate-lines"],
|
"multiline-comment-style": ["error", "separate-lines"],
|
||||||
"multiline-ternary": "off",
|
"multiline-ternary": "off",
|
||||||
|
|
15
README.md
15
README.md
|
@ -360,6 +360,21 @@ title: Title
|
||||||
|
|
||||||
Note: Matches must occur at the start of the file.
|
Note: Matches must occur at the start of the file.
|
||||||
|
|
||||||
|
##### options.handleRuleFailures
|
||||||
|
|
||||||
|
Type: `Boolean`
|
||||||
|
|
||||||
|
Catches exceptions thrown during rule processing and reports the problem as a
|
||||||
|
rule violation.
|
||||||
|
|
||||||
|
By default, exceptions thrown by rules (or the library itself) are unhandled
|
||||||
|
and bubble up the stack to the caller in the conventional manner. By setting
|
||||||
|
`handleRuleFailures` to `true`, exceptions thrown by failing rules will be
|
||||||
|
handled by the library and the exception message logged as a rule violation.
|
||||||
|
This setting can be useful in the presence of (custom) rules that encounter
|
||||||
|
unexpected syntax and fail. By enabling this option, the linting process is
|
||||||
|
allowed to continue and report any violations that were found.
|
||||||
|
|
||||||
##### options.noInlineConfig
|
##### options.noInlineConfig
|
||||||
|
|
||||||
Type: `Boolean`
|
Type: `Boolean`
|
||||||
|
|
|
@ -301,6 +301,7 @@ function lintContent(
|
||||||
md,
|
md,
|
||||||
config,
|
config,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
callback) {
|
callback) {
|
||||||
|
@ -376,7 +377,18 @@ function lintContent(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Call (possibly external) rule function
|
// Call (possibly external) rule function
|
||||||
rule.function(params, onError);
|
if (handleRuleFailures) {
|
||||||
|
try {
|
||||||
|
rule.function(params, onError);
|
||||||
|
} catch (ex) {
|
||||||
|
onError({
|
||||||
|
"lineNumber": 1,
|
||||||
|
"detail": `This rule threw an exception: ${ex.message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rule.function(params, onError);
|
||||||
|
}
|
||||||
// Record any errors (significant performance benefit from length check)
|
// Record any errors (significant performance benefit from length check)
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
errors.sort(lineNumberComparison);
|
errors.sort(lineNumberComparison);
|
||||||
|
@ -432,6 +444,7 @@ function lintFile(
|
||||||
md,
|
md,
|
||||||
config,
|
config,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
synchronous,
|
synchronous,
|
||||||
|
@ -441,7 +454,7 @@ function lintFile(
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
return lintContent(ruleList, file, content, md, config, frontMatter,
|
return lintContent(ruleList, file, content, md, config, frontMatter,
|
||||||
noInlineConfig, resultVersion, callback);
|
handleRuleFailures, noInlineConfig, resultVersion, callback);
|
||||||
}
|
}
|
||||||
// Make a/synchronous call to read file
|
// Make a/synchronous call to read file
|
||||||
if (synchronous) {
|
if (synchronous) {
|
||||||
|
@ -472,6 +485,7 @@ function lintInput(options, synchronous, callback) {
|
||||||
const config = options.config || { "default": true };
|
const config = options.config || { "default": true };
|
||||||
const frontMatter = (options.frontMatter === undefined) ?
|
const frontMatter = (options.frontMatter === undefined) ?
|
||||||
helpers.frontMatterRe : options.frontMatter;
|
helpers.frontMatterRe : options.frontMatter;
|
||||||
|
const handleRuleFailures = !!options.handleRuleFailures;
|
||||||
const noInlineConfig = !!options.noInlineConfig;
|
const noInlineConfig = !!options.noInlineConfig;
|
||||||
const resultVersion = (options.resultVersion === undefined) ?
|
const resultVersion = (options.resultVersion === undefined) ?
|
||||||
2 : options.resultVersion;
|
2 : options.resultVersion;
|
||||||
|
@ -504,6 +518,7 @@ function lintInput(options, synchronous, callback) {
|
||||||
md,
|
md,
|
||||||
config,
|
config,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
lintNextItemCallback);
|
lintNextItemCallback);
|
||||||
|
@ -515,6 +530,7 @@ function lintInput(options, synchronous, callback) {
|
||||||
md,
|
md,
|
||||||
config,
|
config,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
|
handleRuleFailures,
|
||||||
noInlineConfig,
|
noInlineConfig,
|
||||||
resultVersion,
|
resultVersion,
|
||||||
synchronous,
|
synchronous,
|
||||||
|
|
|
@ -966,6 +966,7 @@ module.exports.readmeHeadings = function readmeHeadings(test) {
|
||||||
"##### options.strings",
|
"##### options.strings",
|
||||||
"##### options.config",
|
"##### options.config",
|
||||||
"##### options.frontMatter",
|
"##### options.frontMatter",
|
||||||
|
"##### options.handleRuleFailures",
|
||||||
"##### options.noInlineConfig",
|
"##### options.noInlineConfig",
|
||||||
"##### options.resultVersion",
|
"##### options.resultVersion",
|
||||||
"##### options.markdownItPlugins",
|
"##### options.markdownItPlugins",
|
||||||
|
@ -2408,37 +2409,10 @@ function customRulesUsedTagName(test) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.customRulesThrowForFile =
|
module.exports.customRulesThrowForFile =
|
||||||
function customRulesThrowForFile(test) {
|
function customRulesThrowForFile(test) {
|
||||||
test.expect(4);
|
test.expect(4);
|
||||||
const exceptionMessage = "Test exception message";
|
const exceptionMessage = "Test exception message";
|
||||||
markdownlint({
|
markdownlint({
|
||||||
"customRules": [
|
|
||||||
{
|
|
||||||
"names": [ "name" ],
|
|
||||||
"description": "description",
|
|
||||||
"tags": [ "tag" ],
|
|
||||||
"function": function throws() {
|
|
||||||
throw new Error(exceptionMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"files": [ "./test/custom-rules.md" ]
|
|
||||||
}, function callback(err, result) {
|
|
||||||
test.ok(err, "Did not get an error for function thrown.");
|
|
||||||
test.ok(err instanceof Error, "Error not instance of Error.");
|
|
||||||
test.equal(err.message, exceptionMessage,
|
|
||||||
"Incorrect message for function thrown.");
|
|
||||||
test.ok(!result, "Got result for function thrown.");
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.customRulesThrowForFileSync =
|
|
||||||
function customRulesThrowForFileSync(test) {
|
|
||||||
test.expect(4);
|
|
||||||
const exceptionMessage = "Test exception message";
|
|
||||||
test.throws(function customRuleThrowsCall() {
|
|
||||||
markdownlint.sync({
|
|
||||||
"customRules": [
|
"customRules": [
|
||||||
{
|
{
|
||||||
"names": [ "name" ],
|
"names": [ "name" ],
|
||||||
|
@ -2450,44 +2424,71 @@ function customRulesThrowForFileSync(test) {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"files": [ "./test/custom-rules.md" ]
|
"files": [ "./test/custom-rules.md" ]
|
||||||
|
}, function callback(err, result) {
|
||||||
|
test.ok(err, "Did not get an error for function thrown.");
|
||||||
|
test.ok(err instanceof Error, "Error not instance of Error.");
|
||||||
|
test.equal(err.message, exceptionMessage,
|
||||||
|
"Incorrect message for function thrown.");
|
||||||
|
test.ok(!result, "Got result for function thrown.");
|
||||||
|
test.done();
|
||||||
});
|
});
|
||||||
}, function testError(err) {
|
};
|
||||||
test.ok(err, "Did not get an error for function thrown.");
|
|
||||||
test.ok(err instanceof Error, "Error not instance of Error.");
|
module.exports.customRulesThrowForFileSync =
|
||||||
test.equal(err.message, exceptionMessage,
|
function customRulesThrowForFileSync(test) {
|
||||||
"Incorrect message for function thrown.");
|
test.expect(4);
|
||||||
return true;
|
const exceptionMessage = "Test exception message";
|
||||||
}, "Did not get exception for function thrown.");
|
test.throws(function customRuleThrowsCall() {
|
||||||
test.done();
|
markdownlint.sync({
|
||||||
};
|
"customRules": [
|
||||||
|
{
|
||||||
|
"names": [ "name" ],
|
||||||
|
"description": "description",
|
||||||
|
"tags": [ "tag" ],
|
||||||
|
"function": function throws() {
|
||||||
|
throw new Error(exceptionMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [ "./test/custom-rules.md" ]
|
||||||
|
});
|
||||||
|
}, function testError(err) {
|
||||||
|
test.ok(err, "Did not get an error for function thrown.");
|
||||||
|
test.ok(err instanceof Error, "Error not instance of Error.");
|
||||||
|
test.equal(err.message, exceptionMessage,
|
||||||
|
"Incorrect message for function thrown.");
|
||||||
|
return true;
|
||||||
|
}, "Did not get exception for function thrown.");
|
||||||
|
test.done();
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.customRulesThrowForString =
|
module.exports.customRulesThrowForString =
|
||||||
function customRulesThrowForString(test) {
|
function customRulesThrowForString(test) {
|
||||||
test.expect(4);
|
test.expect(4);
|
||||||
const exceptionMessage = "Test exception message";
|
const exceptionMessage = "Test exception message";
|
||||||
markdownlint({
|
markdownlint({
|
||||||
"customRules": [
|
"customRules": [
|
||||||
{
|
{
|
||||||
"names": [ "name" ],
|
"names": [ "name" ],
|
||||||
"description": "description",
|
"description": "description",
|
||||||
"tags": [ "tag" ],
|
"tags": [ "tag" ],
|
||||||
"function": function throws() {
|
"function": function throws() {
|
||||||
throw new Error(exceptionMessage);
|
throw new Error(exceptionMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"strings": {
|
||||||
|
"string": "String"
|
||||||
}
|
}
|
||||||
],
|
}, function callback(err, result) {
|
||||||
"strings": {
|
test.ok(err, "Did not get an error for function thrown.");
|
||||||
"string": "String"
|
test.ok(err instanceof Error, "Error not instance of Error.");
|
||||||
}
|
test.equal(err.message, exceptionMessage,
|
||||||
}, function callback(err, result) {
|
"Incorrect message for function thrown.");
|
||||||
test.ok(err, "Did not get an error for function thrown.");
|
test.ok(!result, "Got result for function thrown.");
|
||||||
test.ok(err instanceof Error, "Error not instance of Error.");
|
test.done();
|
||||||
test.equal(err.message, exceptionMessage,
|
});
|
||||||
"Incorrect message for function thrown.");
|
};
|
||||||
test.ok(!result, "Got result for function thrown.");
|
|
||||||
test.done();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.customRulesOnErrorNull = function customRulesOnErrorNull(test) {
|
module.exports.customRulesOnErrorNull = function customRulesOnErrorNull(test) {
|
||||||
test.expect(4);
|
test.expect(4);
|
||||||
|
@ -2683,6 +2684,139 @@ module.exports.customRulesOnErrorLazy = function customRulesOnErrorLazy(test) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.customRulesThrowForFileHandled =
|
||||||
|
function customRulesThrowForFileHandled(test) {
|
||||||
|
test.expect(2);
|
||||||
|
const exceptionMessage = "Test exception message";
|
||||||
|
markdownlint({
|
||||||
|
"customRules": [
|
||||||
|
{
|
||||||
|
"names": [ "name" ],
|
||||||
|
"description": "description",
|
||||||
|
"tags": [ "tag" ],
|
||||||
|
"function": function throws() {
|
||||||
|
throw new Error(exceptionMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [ "./test/custom-rules.md" ],
|
||||||
|
"handleRuleFailures": true
|
||||||
|
}, function callback(err, actualResult) {
|
||||||
|
test.ifError(err);
|
||||||
|
const expectedResult = {
|
||||||
|
"./test/custom-rules.md": [
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"ruleNames": [ "name" ],
|
||||||
|
"ruleDescription": "description",
|
||||||
|
"ruleInformation": null,
|
||||||
|
"errorDetail":
|
||||||
|
`This rule threw an exception: ${exceptionMessage}`,
|
||||||
|
"errorContext": null,
|
||||||
|
"errorRange": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.customRulesThrowForStringHandled =
|
||||||
|
function customRulesThrowForStringHandled(test) {
|
||||||
|
test.expect(2);
|
||||||
|
const exceptionMessage = "Test exception message";
|
||||||
|
const informationUrl = "https://example.com/rule";
|
||||||
|
markdownlint({
|
||||||
|
"customRules": [
|
||||||
|
{
|
||||||
|
"names": [ "name" ],
|
||||||
|
"description": "description",
|
||||||
|
"information": new URL(informationUrl),
|
||||||
|
"tags": [ "tag" ],
|
||||||
|
"function": function throws() {
|
||||||
|
throw new Error(exceptionMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"strings": {
|
||||||
|
"string": "String\n"
|
||||||
|
},
|
||||||
|
"handleRuleFailures": true
|
||||||
|
}, function callback(err, actualResult) {
|
||||||
|
test.ifError(err);
|
||||||
|
const expectedResult = {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"ruleNames": [ "MD041", "first-line-heading", "first-line-h1" ],
|
||||||
|
"ruleDescription":
|
||||||
|
"First line in file should be a top level heading",
|
||||||
|
"ruleInformation":
|
||||||
|
`${homepage}/blob/v${version}/doc/Rules.md#md041`,
|
||||||
|
"errorDetail": null,
|
||||||
|
"errorContext": "String",
|
||||||
|
"errorRange": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"ruleNames": [ "name" ],
|
||||||
|
"ruleDescription": "description",
|
||||||
|
"ruleInformation": informationUrl,
|
||||||
|
"errorDetail":
|
||||||
|
`This rule threw an exception: ${exceptionMessage}`,
|
||||||
|
"errorContext": null,
|
||||||
|
"errorRange": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.customRulesOnErrorInvalidHandled =
|
||||||
|
function customRulesOnErrorInvalidHandled(test) {
|
||||||
|
test.expect(2);
|
||||||
|
markdownlint({
|
||||||
|
"customRules": [
|
||||||
|
{
|
||||||
|
"names": [ "name" ],
|
||||||
|
"description": "description",
|
||||||
|
"tags": [ "tag" ],
|
||||||
|
"function": function onErrorInvalid(params, onError) {
|
||||||
|
onError({
|
||||||
|
"lineNumber": 13,
|
||||||
|
"detail": "N/A"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"strings": {
|
||||||
|
"string": "# Heading\n"
|
||||||
|
},
|
||||||
|
"handleRuleFailures": true
|
||||||
|
}, function callback(err, actualResult) {
|
||||||
|
test.ifError(err);
|
||||||
|
const expectedResult = {
|
||||||
|
"string": [
|
||||||
|
{
|
||||||
|
"lineNumber": 1,
|
||||||
|
"ruleNames": [ "name" ],
|
||||||
|
"ruleDescription": "description",
|
||||||
|
"ruleInformation": null,
|
||||||
|
"errorDetail": "This rule threw an exception: " +
|
||||||
|
"Property 'lineNumber' of onError parameter is incorrect.",
|
||||||
|
"errorContext": null,
|
||||||
|
"errorRange": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
|
||||||
|
test.done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.customRulesFileName = function customRulesFileName(test) {
|
module.exports.customRulesFileName = function customRulesFileName(test) {
|
||||||
test.expect(2);
|
test.expect(2);
|
||||||
const options = {
|
const options = {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue