diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 97e8ba88..ae7a6f56 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -58,6 +58,7 @@ function lintContent(content, config) { // Parse content into tokens and lines var tokens = md.parse(content, {}); var lines = content.split(shared.newLineRe); + var tokenLists = {}; // Annotate tokens with line/lineNumber tokens.forEach(function forToken(token) { if (token.map) { @@ -76,10 +77,15 @@ function lintContent(content, config) { } }); } + if (!tokenLists[token.type]) { + tokenLists[token.type] = []; + } + tokenLists[token.type].push(token); }); // Create parameters for rules var params = { "tokens": tokens, + "tokenLists": tokenLists, "lines": lines }; // Merge rules/tags and sanitize config diff --git a/lib/rules.js b/lib/rules.js index 16620dcf..bdff6a1e 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -32,19 +32,15 @@ function unorderedListStyleFor(token) { } // Calls the provided function for each matching token -function filterTokens(tokens, type, callback) { - tokens.forEach(function forToken(token) { - if (token.type === type) { - callback(token); - } - }); +function filterTokens(params, type, callback) { + (params.tokenLists[type] || []).forEach(callback); } // Calls the provided function for each line (with context) function forEachLine(params, callback) { // Identify lines in code blocks var codeLines = []; - filterTokens(params.tokens, "code_block", function forToken(token) { + filterTokens(params, "code_block", function forToken(token) { for (var i = token.map[0]; i < token.map[1]; i++) { codeLines.push(i); } @@ -63,9 +59,11 @@ function forEachLine(params, callback) { // Calls the provided function for each specified inline child token function forEachInlineChild(params, type, callback) { - filterTokens(params.tokens, "inline", function forToken(token) { - filterTokens(token.children, type, function forChild(child) { - callback(child, token); + filterTokens(params, "inline", function forToken(token) { + token.children.forEach(function forChild(child) { + if (child.type === type) { + callback(child, token); + } }); }); } @@ -131,7 +129,7 @@ module.exports = [ "tags": [ "headers" ], "func": function MD001(params, errors) { var prevLevel = 0; - filterTokens(params.tokens, "heading_open", function forToken(token) { + filterTokens(params, "heading_open", function forToken(token) { var level = parseInt(token.tag.slice(1), 10); if (prevLevel && (level > prevLevel + 1)) { errors.push(token.lineNumber); @@ -164,7 +162,7 @@ module.exports = [ "tags": [ "headers" ], "func": function MD003(params, errors) { var style = params.options.style || "consistent"; - filterTokens(params.tokens, "heading_open", function forToken(token) { + filterTokens(params, "heading_open", function forToken(token) { if (style === "consistent") { style = headingStyleFor(token); } @@ -324,7 +322,7 @@ module.exports = [ "tags": [ "code" ], "func": function MD014(params, errors) { [ "code_block", "fence" ].forEach(function forType(type) { - filterTokens(params.tokens, type, function forToken(token) { + filterTokens(params, type, function forToken(token) { if (token.content && token.content.split(shared.newLineRe) .every(function forLine(line) { return !line || /^\$\s/.test(line); @@ -354,7 +352,7 @@ module.exports = [ "desc": "Multiple spaces after hash on atx style header", "tags": [ "headers", "atx", "spaces" ], "func": function MD019(params, errors) { - filterTokens(params.tokens, "heading_open", function forToken(token) { + filterTokens(params, "heading_open", function forToken(token) { if ((headingStyleFor(token) === "atx") && /^#+\s\s/.test(token.line)) { errors.push(token.lineNumber); @@ -382,7 +380,7 @@ module.exports = [ "desc": "Multiple spaces inside hashes on closed atx style header", "tags": [ "headers", "atx_closed", "spaces" ], "func": function MD021(params, errors) { - filterTokens(params.tokens, "heading_open", function forToken(token) { + filterTokens(params, "heading_open", function forToken(token) { if ((headingStyleFor(token) === "atx_closed") && (/^#+\s\s/.test(token.line) || /\s\s#+$/.test(token.line))) { errors.push(token.lineNumber); @@ -433,7 +431,7 @@ module.exports = [ "desc": "Headers must start at the beginning of the line", "tags": [ "headers", "spaces" ], "func": function MD023(params, errors) { - filterTokens(params.tokens, "heading_open", function forToken(token) { + filterTokens(params, "heading_open", function forToken(token) { if (/^\s/.test(token.line)) { errors.push(token.lineNumber); } @@ -463,7 +461,7 @@ module.exports = [ "tags": [ "headers" ], "func": function MD025(params, errors) { var hasTopLevelHeading = false; - filterTokens(params.tokens, "heading_open", function forToken(token) { + filterTokens(params, "heading_open", function forToken(token) { if (token.tag === "h1") { if (hasTopLevelHeading) { errors.push(token.lineNumber); @@ -623,7 +621,7 @@ module.exports = [ "desc": "Inline HTML", "tags": [ "html" ], "func": function MD033(params, errors) { - filterTokens(params.tokens, "html_block", function forToken(token) { + filterTokens(params, "html_block", function forToken(token) { errors.push(token.lineNumber); }); forEachInlineChild(params, "html_inline", function forToken(token) { @@ -637,7 +635,7 @@ module.exports = [ "desc": "Bare URL used", "tags": [ "links", "url" ], "func": function MD034(params, errors) { - filterTokens(params.tokens, "inline", function forToken(token) { + filterTokens(params, "inline", function forToken(token) { var inLink = false; token.children.forEach(function forChild(child) { if (child.type === "link_open") { @@ -660,7 +658,7 @@ module.exports = [ "tags": [ "hr" ], "func": function MD035(params, errors) { var style = params.options.style || "consistent"; - filterTokens(params.tokens, "hr", function forToken(token) { + filterTokens(params, "hr", function forToken(token) { if (style === "consistent") { style = token.line; } @@ -735,7 +733,7 @@ module.exports = [ "desc": "Spaces inside link text", "tags": [ "whitespace", "links" ], "func": function MD039(params, errors) { - filterTokens(params.tokens, "inline", function forToken(token) { + filterTokens(params, "inline", function forToken(token) { var inLink = false; var index = 0; var lastChildRightSpaceLineNumber = 0; @@ -768,7 +766,7 @@ module.exports = [ "desc": "Fenced code blocks should have a language specified", "tags": [ "code", "language" ], "func": function MD040(params, errors) { - filterTokens(params.tokens, "fence", function forToken(token) { + filterTokens(params, "fence", function forToken(token) { if (!token.info.trim()) { errors.push(token.lineNumber); }