diff --git a/lib/rules.js b/lib/rules.js index 3fc2962e..c317c2fa 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -53,6 +53,19 @@ function forEachLine(params, callback) { }); } +function forEachHeading(params, callback) { + var heading = null; + params.tokens.forEach(function forToken(token) { + if (token.type === "heading_open") { + heading = token; + } else if (token.type === "heading_close") { + heading = null; + } else if ((token.type === "inline") && heading) { + callback(heading, token.content); + } + }); +} + function flattenLists(tokens, filterBy) { var lists = []; var stack = []; @@ -273,8 +286,7 @@ module.exports = [ "func": function MD014(params, errors) { filterTokens(params.tokens, "code_block", "fence") .forEach(function forToken(token) { - if (token.content && token.content - .split(shared.newLineRe) + if (token.content && token.content.split(shared.newLineRe) .filter(function filterLine(line) { return line; }).every(function forLine(line) { @@ -392,19 +404,12 @@ module.exports = [ "name": "MD024", "desc": "Multiple headers with the same content", "func": function MD024(params, errors) { - var content = []; - var inHeading = false; - params.tokens.forEach(function forToken(token) { - if (token.type === "heading_open") { - inHeading = true; - } else if (token.type === "heading_close") { - inHeading = false; - } else if ((token.type === "inline") && inHeading) { - if (content.indexOf(token.content) === -1) { - content.push(token.content); - } else { - errors.push(token.lineNumber); - } + var knownContent = []; + forEachHeading(params, function forHeading(heading, content) { + if (knownContent.indexOf(content) === -1) { + knownContent.push(content); + } else { + errors.push(heading.lineNumber); } }); } @@ -428,6 +433,43 @@ module.exports = [ } }, + { + "name": "MD026", + "desc": "Trailing punctuation in header", + "func": function MD026(params, errors) { + var punctuation = params.options.punctuation || ".,;:!?"; + var re = new RegExp("[" + punctuation + "]$"); + forEachHeading(params, function forHeading(heading, content) { + if (re.test(content)) { + errors.push(heading.lineNumber); + } + }); + } + }, + + { + "name": "MD027", + "desc": "Multiple spaces after blockquote symbol", + "func": function MD027(params, errors) { + var inBlockquote = false; + params.tokens.forEach(function forToken(token) { + if (token.type === "blockquote_open") { + inBlockquote = true; + } else if (token.type === "blockquote_close") { + inBlockquote = false; + } else if ((token.type === "inline") && inBlockquote) { + token.content.split(shared.newLineRe) + .forEach(function forLine(line, offset) { + if (/^\s/.test(line) || + (!offset && /^\s*>\s\s/.test(token.line))) { + errors.push(token.lineNumber + offset); + } + }); + } + }); + } + }, + { "name": "MD028", "desc": "Blank line inside blockquote", diff --git a/test/blockquote_spaces.md b/test/blockquote_spaces.md new file mode 100644 index 00000000..7492e7d2 --- /dev/null +++ b/test/blockquote_spaces.md @@ -0,0 +1,21 @@ +Some text + +> Hello world +> Foo {MD027} +> Bar {MD027} + +This tests other things embedded in the blockquote: + +> *Hello world* +> *foo* {MD027} +> **bar** {MD027} +> "Baz" {MD027} +> *foo* +> **bar** +> 'baz' + +Test the first line being indented too much: + +> Foo {MD027} +> Bar {MD027} +> Baz \ No newline at end of file diff --git a/test/header_trailing_punctuation.md b/test/header_trailing_punctuation.md new file mode 100644 index 00000000..81d4fabf --- /dev/null +++ b/test/header_trailing_punctuation.md @@ -0,0 +1,11 @@ +# Heading 1 {MD026}. + +## Heading 2 {MD026}, + +## Heading 3 {MD026}! + +## Heading 4 {MD026}: + +## Heading 5 {MD026}; + +## Heading 6 {MD026}? \ No newline at end of file diff --git a/test/header_trailing_punctuation_customized.json b/test/header_trailing_punctuation_customized.json new file mode 100644 index 00000000..df827f91 --- /dev/null +++ b/test/header_trailing_punctuation_customized.json @@ -0,0 +1,6 @@ +{ + "default": true, + "MD026": { + "punctuation": ".,;:!" + } +} diff --git a/test/header_trailing_punctuation_customized.md b/test/header_trailing_punctuation_customized.md new file mode 100644 index 00000000..e44f5482 --- /dev/null +++ b/test/header_trailing_punctuation_customized.md @@ -0,0 +1,14 @@ +# Heading 1 {MD026}. + +## Heading 2 {MD026}, + +## Heading 3 {MD026}! + +## Heading 4 {MD026}: + +## Heading 5 {MD026}; + +## Heading 6? + +The rule has been customized to allow question marks while disallowing +everything else.