Add tags to README.md, new test and fix, comment rule code.

This commit is contained in:
David Anson 2015-03-16 22:31:18 -07:00
parent 63a52e9dea
commit 0bd6dea637
5 changed files with 138 additions and 47 deletions

View file

@ -2,10 +2,12 @@
var shared = require("./shared");
// Returns the indent for a token
function indentFor(token) {
return token.line.length - token.line.trimLeft().length;
}
// Returns the heading style for a heading token
function headingStyleFor(token) {
if ((token.lines[1] - token.lines[0]) === 1) {
if (/#\s*$/.test(token.line)) {
@ -16,6 +18,7 @@ function headingStyleFor(token) {
return "setext";
}
// Returns the unordered list style for a list item token
function unorderedListStyleFor(token) {
switch (token.line.trimLeft().substr(0, 1)) {
case "-":
@ -28,31 +31,36 @@ function unorderedListStyleFor(token) {
}
}
function filterTokens(tokens, tokenA, tokenB) {
// Filters a list of tokens by type
function filterTokens(tokens, typeA, typeB) {
return tokens.filter(function filterToken(token) {
return ((token.type === tokenA) || (token.type === tokenB));
return ((token.type === typeA) || (token.type === typeB));
});
}
// Calls the provided function for each line (with context)
function forEachLine(params, callback) {
var codeBlocks = [];
// Identify lines in code blocks
var codeLines = [];
filterTokens(params.tokens, "code_block")
.forEach(function forToken(token) {
for (var i = token.lines[0]; i < token.lines[1]; i++) {
codeBlocks.push(i);
codeLines.push(i);
}
});
// Identify lines in code fences (with info about transitions)
var inFence = false;
params.lines.forEach(function forLine(line, lineIndex) {
var onFence = /^(```|~~~)/.test(line);
if (onFence) {
inFence = !inFence;
}
var codeBlock = (codeBlocks.indexOf(lineIndex) !== -1);
callback(line, lineIndex, inFence || codeBlock, onFence);
var inCodeBlock = (codeLines.indexOf(lineIndex) !== -1);
callback(line, lineIndex, inFence || inCodeBlock, onFence);
});
}
// Calls the provided function for each heading's content
function forEachHeading(params, callback) {
var heading = null;
params.tokens.forEach(function forToken(token) {
@ -66,6 +74,7 @@ function forEachHeading(params, callback) {
});
}
// Returns (nested) lists as a flat array (in order)
function flattenLists(tokens, filterBy) {
var lists = [];
var stack = [];
@ -74,6 +83,7 @@ function flattenLists(tokens, filterBy) {
tokens.forEach(function forToken(token) {
if ((token.type === "bullet_list_open") ||
(token.type === "ordered_list_open")) {
// Save current context and start a new one
stack.push(current);
current = {
"ordered": (token.type === "ordered_list_open"),
@ -85,6 +95,7 @@ function flattenLists(tokens, filterBy) {
};
} else if ((token.type === "bullet_list_close") ||
(token.type === "ordered_list_close")) {
// Finalize current context and restore previous
current.lastLineIndex = lastWithLines.lines[1];
if ((filterBy === undefined) || (filterBy === current.ordered)) {
lists.splice(current.insert, 0, current);
@ -92,8 +103,10 @@ function flattenLists(tokens, filterBy) {
}
current = stack.pop();
} else if (token.type === "list_item_open") {
// Add list item
current.items.push(token);
} else if (token.lines) {
// Track last token with lines
lastWithLines = token;
}
});
@ -104,6 +117,7 @@ module.exports = [
{
"name": "MD001",
"desc": "Header levels should only increment by one level at a time",
"tags": [ "headers" ],
"func": function MD001(params, errors) {
var prevLevel = 0;
filterTokens(params.tokens, "heading_open")
@ -119,6 +133,7 @@ module.exports = [
{
"name": "MD002",
"desc": "First header should be a h1 header",
"tags": [ "headers" ],
"func": function MD002(params, errors) {
params.tokens.every(function forToken(token) {
if (token.type === "heading_open") {
@ -135,6 +150,7 @@ module.exports = [
{
"name": "MD003",
"desc": "Header style",
"tags": [ "headers" ],
"func": function MD003(params, errors) {
var style = params.options.style || "consistent";
var headings = filterTokens(params.tokens, "heading_open");
@ -152,6 +168,7 @@ module.exports = [
{
"name": "MD004",
"desc": "Unordered list style",
"tags": [ "bullet", "ul" ],
"func": function MD004(params, errors) {
var style = params.options.style || "consistent";
flattenLists(params.tokens, false).forEach(function forList(list) {
@ -170,6 +187,7 @@ module.exports = [
{
"name": "MD005",
"desc": "Inconsistent indentation for list items at the same level",
"tags": [ "bullet", "ul", "indentation" ],
"func": function MD005(params, errors) {
flattenLists(params.tokens).forEach(function forList(list) {
var indent = indentFor(list.items[0]);
@ -185,6 +203,7 @@ module.exports = [
{
"name": "MD006",
"desc": "Consider starting bulleted lists at the beginning of the line",
"tags": [ "bullet", "ul", "indentation" ],
"func": function MD006(params, errors) {
flattenLists(params.tokens, false).forEach(function forList(list) {
if (!list.nesting && indentFor(list.open)) {
@ -197,6 +216,7 @@ module.exports = [
{
"name": "MD007",
"desc": "Unordered list indentation",
"tags": [ "bullet", "ul", "indentation" ],
"func": function MD007(params, errors) {
var optionsIndent = params.options.indent || 2;
var prevIndent = 0;
@ -214,6 +234,7 @@ module.exports = [
{
"name": "MD009",
"desc": "Trailing spaces",
"tags": [ "whitespace" ],
"func": function MD009(params, errors) {
params.lines.forEach(function forLine(line, lineIndex) {
if (/\s$/.test(line)) {
@ -226,6 +247,7 @@ module.exports = [
{
"name": "MD010",
"desc": "Hard tabs",
"tags": [ "whitespace" ],
"func": function MD010(params, errors) {
params.lines.forEach(function forLine(line, lineIndex) {
if (/\t/.test(line)) {
@ -238,6 +260,7 @@ module.exports = [
{
"name": "MD011",
"desc": "Reversed link syntax",
"tags": [ "links" ],
"func": function MD011(params, errors) {
filterTokens(params.tokens, "inline")
.forEach(function forToken(token) {
@ -254,6 +277,7 @@ module.exports = [
{
"name": "MD012",
"desc": "Multiple consecutive blank lines",
"tags": [ "whitespace", "blank_lines" ],
"func": function MD012(params, errors) {
var prevLine = "-";
forEachLine(params, function forLine(line, lineIndex, inCode) {
@ -269,11 +293,12 @@ module.exports = [
{
"name": "MD013",
"desc": "Line length",
"tags": [ "line_length" ],
"func": function MD013(params, errors) {
var lineLength = params.options.line_length || 80;
var regex = new RegExp("^.{" + lineLength + "}.*\\s");
var re = new RegExp("^.{" + lineLength + "}.*\\s");
params.lines.forEach(function forLine(line, lineIndex) {
if (regex.test(line)) {
if (re.test(line)) {
errors.push(lineIndex + 1);
}
});
@ -283,6 +308,7 @@ module.exports = [
{
"name": "MD014",
"desc": "Dollar signs used before commands without showing output",
"tags": [ "code" ],
"func": function MD014(params, errors) {
filterTokens(params.tokens, "code_block", "fence")
.forEach(function forToken(token) {
@ -301,6 +327,7 @@ module.exports = [
{
"name": "MD018",
"desc": "No space after hash on atx style header",
"tags": [ "headers", "atx", "spaces" ],
"func": function MD018(params, errors) {
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) {
@ -313,6 +340,7 @@ module.exports = [
{
"name": "MD019",
"desc": "Multiple spaces after hash on atx style header",
"tags": [ "headers", "atx", "spaces" ],
"func": function MD019(params, errors) {
filterTokens(params.tokens, "heading_open")
.forEach(function forToken(token) {
@ -327,6 +355,7 @@ module.exports = [
{
"name": "MD020",
"desc": "No space inside hashes on closed atx style header",
"tags": [ "headers", "atx_closed", "spaces" ],
"func": function MD020(params, errors) {
forEachLine(params, function forLine(line, lineIndex, inCode) {
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line) &&
@ -340,6 +369,7 @@ module.exports = [
{
"name": "MD021",
"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")
.forEach(function forToken(token) {
@ -354,6 +384,7 @@ module.exports = [
{
"name": "MD022",
"desc": "Headers should be surrounded by blank lines",
"tags": [ "headers", "blank_lines" ],
"func": function MD022(params, errors) {
var prevHeadingLineNumber = 0;
var prevMaxLineIndex = -1;
@ -390,6 +421,7 @@ module.exports = [
{
"name": "MD023",
"desc": "Headers must start at the beginning of the line",
"tags": [ "headers", "spaces" ],
"func": function MD023(params, errors) {
filterTokens(params.tokens, "heading_open")
.forEach(function forToken(token) {
@ -403,6 +435,7 @@ module.exports = [
{
"name": "MD024",
"desc": "Multiple headers with the same content",
"tags": [ "headers" ],
"func": function MD024(params, errors) {
var knownContent = [];
forEachHeading(params, function forHeading(heading, content) {
@ -418,6 +451,7 @@ module.exports = [
{
"name": "MD025",
"desc": "Multiple top level headers in the same document",
"tags": [ "headers" ],
"func": function MD025(params, errors) {
var hasTopLevelHeading = false;
filterTokens(params.tokens, "heading_open")
@ -436,6 +470,7 @@ module.exports = [
{
"name": "MD026",
"desc": "Trailing punctuation in header",
"tags": [ "headers" ],
"func": function MD026(params, errors) {
var punctuation = params.options.punctuation || ".,;:!?";
var re = new RegExp("[" + punctuation + "]$");
@ -450,6 +485,7 @@ module.exports = [
{
"name": "MD027",
"desc": "Multiple spaces after blockquote symbol",
"tags": [ "blockquote", "whitespace", "indentation" ],
"func": function MD027(params, errors) {
var inBlockquote = false;
params.tokens.forEach(function forToken(token) {
@ -473,6 +509,7 @@ module.exports = [
{
"name": "MD028",
"desc": "Blank line inside blockquote",
"tags": [ "blockquote", "whitespace" ],
"func": function MD028(params, errors) {
var prevToken = {};
params.tokens.forEach(function forToken(token) {
@ -488,6 +525,7 @@ module.exports = [
{
"name": "MD029",
"desc": "Ordered list item prefix",
"tags": [ "ol" ],
"func": function MD029(params, errors) {
var style = params.options.style || "one";
flattenLists(params.tokens, true).forEach(function forList(list) {
@ -508,6 +546,7 @@ module.exports = [
{
"name": "MD030",
"desc": "Spaces after list markers",
"tags": [ "ol", "ul", "whitespace" ],
"func": function MD030(params, errors) {
var ulSingle = params.options.ul_single || 1;
var olSingle = params.options.ol_single || 1;
@ -532,9 +571,8 @@ module.exports = [
{
"name": "MD031",
"desc": "Fenced code blocks should be surrounded by blank lines",
"tags": [ "code", "blank_lines" ],
"func": function MD031(params, errors) {
// Some parsers have trouble detecting fenced code blocks without
// surrounding whitespace, so examine the lines directly.
forEachLine(params, function forLine(line, lineIndex, inCode, onFence) {
if (onFence &&
((inCode && (lineIndex - 1 >= 0) &&
@ -550,9 +588,8 @@ module.exports = [
{
"name": "MD032",
"desc": "Lists should be surrounded by blank lines",
"tags": [ "bullet", "ul", "ol", "blank_lines" ],
"func": function MD032(params, errors) {
// Some parsers have trouble detecting lists without surrounding
// whitespace, so examine the lines directly.
var inList = false;
var prevLine = "";
forEachLine(params, function forLine(line, lineIndex, inCode, onFence) {