Refactor code-style to use xo

This commit is contained in:
Titus Wormer 2016-08-01 20:41:43 +02:00
parent d272ff257b
commit 60907ad17e
68 changed files with 2220 additions and 3133 deletions

View File

@ -2,14 +2,8 @@ root = true
[*]
indent_style = space
indent_size = 4
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{json,remarkrc}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View File

@ -1,3 +0,0 @@
coverage/
remark-lint.js
remark-lint.min.js

View File

@ -1,6 +0,0 @@
{
"extends": "eslint:recommended",
"rules": {
"quotes": [2, "single"]
}
}

View File

@ -1,34 +0,0 @@
{
"excludeFiles": [
"coverage/",
"node_modules/",
"remark-lint.js",
"remark-lint.min.js"
],
"preset": "crockford",
"requireMultipleVarDecl": false,
"requireVarDeclFirst": false,
"disallowDanglingUnderscores": false,
"maximumLineLength": false,
"requireQuotedKeysInObjects": true,
"disallowKeywords": [
"with"
],
"jsDoc": {
"checkAnnotations": "jsdoc3",
"checkParamExistence": true,
"checkParamNames": true,
"checkRedundantAccess": true,
"checkRedundantParams": true,
"checkRedundantReturns": true,
"checkReturnTypes": true,
"checkTypes": "strictNativeCase",
"enforceExistence": true,
"requireHyphenBeforeDescription": true,
"requireNewlineAfterDescription": true,
"requireParamDescription": true,
"requireParamTypes": true,
"requireReturnDescription": true,
"requireReturnTypes": true
}
}

View File

@ -1,4 +1 @@
# `node_modules/` is already ignored.
# Do not process fixtures.
test

View File

@ -1,17 +0,0 @@
{
"output": true,
"plugins": {
"comment-config": null,
"github": null,
"toc": {
"tight": true
},
"./": {
"no-missing-blank-lines": false
},
"validate-links": null
},
"settings": {
"bullet": "*"
}
}

View File

@ -11,16 +11,7 @@
/* eslint-env commonjs */
/*
* Constants.
*/
var SOURCE = 'remark-lint';
/*
* Dependencies.
*/
/* Dependencies. */
var decamelize = require('decamelize');
var sort = require('vfile-sort');
var control = require('remark-message-control');
@ -29,42 +20,89 @@ var trough = require('trough');
var wrapped = require('wrapped');
var internals = require('./rules');
/* Expose. */
module.exports = lint;
/* Constants. */
var SOURCE = 'remark-lint';
/**
* Factory to create a plugin from a rule.
* Lint attacher.
*
* By default, all rules are turned on unless explicitly
* set to `false`. When `reset: true`, the opposite is
* true: all rules are turned off, unless when given
* a non-nully and non-false value.
*
* @example
* attachFactory('foo', console.log, false)() // null
* attachFactory('foo', console.log, {})() // plugin
* var processor = lint(remark, {
* html: false // Ignore HTML warnings.
* });
*
* @param {string} id - Identifier.
* @param {Function} rule - Rule
* @param {*} options - Options for respective rule.
* @return {Function} - Trough ware.
* @param {Remark} remark - Host object.
* @param {Object?} options - Hash of rule names mapping to
* rule options.
*/
function ruleFactory(id, rule, options) {
var fn = wrapped(rule);
function lint(remark, options) {
var settings = decamelizeSettings(options || {});
var rules = loadExternals(settings.external);
var reset = options && options.reset;
var enable = [];
var disable = [];
var known = [];
var pipeline = trough();
var setting;
var id;
return function (ast, file, next) {
var scope = file.namespace('remark-lint');
/* Add each rule. */
for (id in rules) {
setting = settings[id];
/* Track new messages per file. */
if (scope.index === undefined || scope.index === null) {
scope.index = file.messages.length;
}
known.push(id);
fn(ast, file, options, function (err) {
var messages = file.messages;
if (setting != null) {
/* Pass turned on rules `undefined`. */
if (reset && setting === true) {
setting = undefined;
}
while (scope.index < messages.length) {
messages[scope.index].ruleId = id;
messages[scope.index].source = SOURCE;
scope.index++;
}
next(err);
});
if (setting === false) {
setting = undefined;
disable.push(id);
} else {
enable.push(id);
}
}
pipeline.use(ruleFactory(id, rules[id], setting));
}
/* Run all rules. */
remark.use(function () {
return function (node, file, next) {
pipeline.run(node, file, next);
};
});
/* Allow comments to toggle messages. */
remark.use(control, {
name: 'lint',
source: SOURCE,
reset: reset,
known: known,
enable: enable,
disable: disable
});
/**
* Transformer to sort messages.
*
* @param {Node} node - Syntax tree.
* @param {VFile} file - Virtual file.
*/
return function (node, file) {
sort(file);
};
}
/**
@ -81,31 +119,69 @@ function ruleFactory(id, rule, options) {
* @throws {Error} - When an external cannot be resolved.
*/
function loadExternals(externals) {
var index = -1;
var rules = {};
var external;
var ruleId;
var mapping = externals ? externals.concat() : [];
var length;
var index = -1;
var rules = {};
var external;
var ruleId;
var mapping = externals ? externals.concat() : [];
var length;
mapping.push(internals);
length = mapping.length;
mapping.push(internals);
length = mapping.length;
while (++index < length) {
external = mapping[index];
while (++index < length) {
external = mapping[index];
if (typeof external === 'string') {
external = loadPlugin(external, {
prefix: 'remark-lint-'
});
}
for (ruleId in external) {
rules[ruleId] = external[ruleId];
}
if (typeof external === 'string') {
external = loadPlugin(external, {
prefix: 'remark-lint-'
});
}
return rules;
for (ruleId in external) {
rules[ruleId] = external[ruleId];
}
}
return rules;
}
/**
* Factory to create a plugin from a rule.
*
* @example
* attachFactory('foo', console.log, false)() // null
* attachFactory('foo', console.log, {})() // plugin
*
* @param {string} id - Identifier.
* @param {Function} rule - Rule
* @param {*} options - Options for respective rule.
* @return {Function} - Trough ware.
*/
function ruleFactory(id, rule, options) {
var fn = wrapped(rule);
return function (ast, file, next) {
var scope = file.namespace('remark-lint');
/* Track new messages per file. */
if (scope.index === undefined || scope.index === null) {
scope.index = file.messages.length;
}
fn(ast, file, options, function (err) {
var messages = file.messages;
while (scope.index < messages.length) {
messages[scope.index].ruleId = id;
messages[scope.index].source = SOURCE;
scope.index++;
}
next(err);
});
};
}
/**
@ -116,106 +192,12 @@ function loadExternals(externals) {
* @return {Object} - Dash-cased settings.
*/
function decamelizeSettings(source) {
var result = {};
var key;
var result = {};
var key;
for (key in source) {
result[decamelize(key, '-')] = source[key];
}
for (key in source) {
result[decamelize(key, '-')] = source[key];
}
return result;
return result;
}
/**
* Lint attacher.
*
* By default, all rules are turned on unless explicitly
* set to `false`. When `reset: true`, the opposite is
* true: all rules are turned off, unless when given
* a non-nully and non-false value.
*
* @example
* var processor = lint(remark, {
* 'html': false // Ignore HTML warnings.
* });
*
* @param {Remark} remark - Host object.
* @param {Object?} options - Hash of rule names mapping to
* rule options.
*/
function lint(remark, options) {
var settings = decamelizeSettings(options || {});
var rules = loadExternals(settings.external);
var reset = options && options.reset;
var enable = [];
var disable = [];
var known = [];
var pipeline = trough();
var setting;
var id;
/*
* Add each rule as a seperate plugin.
*/
for (id in rules) {
setting = settings[id];
known.push(id);
if (!(setting === null || setting === undefined)) {
/* Pass turned on rules `undefined`. */
if (reset && setting === true) {
setting = undefined;
}
if (setting === false) {
setting = undefined;
disable.push(id);
} else {
enable.push(id);
}
}
pipeline.use(ruleFactory(id, rules[id], setting));
}
/*
* Run all rules.
*/
remark.use(function () {
return function (node, file, next) {
pipeline.run(node, file, next);
};
});
/*
* Allow comments to toggle messages.
*/
remark.use(control, {
'name': 'lint',
'source': SOURCE,
'reset': reset,
'known': known,
'enable': enable,
'disable': disable
});
/**
* Transformer sort messages.
*
* @param {Node} node - Syntax tree.
* @param {VFile} file - Virtual file.
*/
return function (node, file) {
sort(file);
};
}
/*
* Expose.
*/
module.exports = lint;

View File

@ -29,35 +29,15 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Expose. */
module.exports = blockquoteIndentation;
/* Dependencies. */
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
var plural = require('plur');
var position = require('unist-util-position');
/**
* Get the indent of a blockquote.
*
* @param {Node} node - Node to test.
* @return {number} - Indentation.
*/
function check(node) {
var head = node.children[0];
var indentation = position.start(head).column - position.start(node).column;
var padding = toString(head).match(/^ +/);
if (padding) {
indentation += padding[0].length;
}
return indentation;
}
/**
* Warn when a blockquote has a too large or too small
* indentation.
@ -68,44 +48,53 @@ function check(node) {
* indentation between a blockquote and its content.
* When not a number, defaults to the first found
* indentation.
* @param {Function} done - Callback.
*/
function blockquoteIndentation(ast, file, preferred, done) {
preferred = isNaN(preferred) || typeof preferred !== 'number' ? null : preferred;
function blockquoteIndentation(ast, file, preferred) {
preferred = isNaN(preferred) || typeof preferred !== 'number' ? null : preferred;
visit(ast, 'blockquote', function (node) {
var indent;
var diff;
var word;
visit(ast, 'blockquote', function (node) {
var indent;
var diff;
var word;
if (position.generated(node) || !node.children.length) {
return;
}
if (position.generated(node) || !node.children.length) {
return;
}
if (preferred) {
indent = check(node);
diff = preferred - indent;
word = diff > 0 ? 'Add' : 'Remove';
if (preferred) {
indent = check(node);
diff = preferred - indent;
word = diff > 0 ? 'Add' : 'Remove';
diff = Math.abs(diff);
diff = Math.abs(diff);
if (diff !== 0) {
file.warn(
word + ' ' + diff + ' ' + plural('space', diff) +
' between blockquote and content',
position.start(node.children[0])
);
}
} else {
preferred = check(node);
}
});
done();
if (diff !== 0) {
file.warn(
word + ' ' + diff + ' ' + plural('space', diff) +
' between blockquote and content',
position.start(node.children[0])
);
}
} else {
preferred = check(node);
}
});
}
/*
* Expose.
/**
* Get the indent of a blockquote.
*
* @param {Node} node - Node to test.
* @return {number} - Indentation.
*/
function check(node) {
var head = node.children[0];
var indentation = position.start(head).column - position.start(node).column;
var padding = toString(head).match(/^ +/);
module.exports = blockquoteIndentation;
if (padding) {
indentation += padding[0].length;
}
return indentation;
}

View File

@ -21,19 +21,19 @@
* @example
* <!-- Note: the double guillemet (`»`) and middle-dots represent a tab -->
*
* <!-- Valid by default, `'consistent'`, or `{'checked': 'x'}` -->
* <!-- Valid by default, `'consistent'`, or `{checked: 'x'}` -->
* - [x] List item
* - [x] List item
*
* <!-- Valid by default, `'consistent'`, or `{'checked': 'X'}` -->
* <!-- Valid by default, `'consistent'`, or `{checked: 'X'}` -->
* - [X] List item
* - [X] List item
*
* <!-- Valid by default, `'consistent'`, or `{'unchecked': ' '}` -->
* <!-- Valid by default, `'consistent'`, or `{unchecked: ' '}` -->
* - [ ] List item
* - [ ] List item
*
* <!-- Valid by default, `'consistent'`, or `{'unchecked': '»'}` -->
* <!-- Valid by default, `'consistent'`, or `{unchecked: '»'}` -->
* - [»···] List item
* - [»···] List item
*
@ -46,32 +46,21 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = checkboxCharacterStyle;
/* Methods. */
var start = position.start;
var end = position.end;
var CHECKED = {
'x': true,
'X': true
};
var UNCHECKED = {
' ': true,
' ': true
};
/* Constants. */
var CHECKED = {x: true, X: true};
var UNCHECKED = {' ': true, '\t': true};
/**
* Warn when list item checkboxes violate a given style.
@ -82,107 +71,89 @@ var UNCHECKED = {
* and `unchecked` properties, each set to null to default to
* the first found style, or set to `'x'` or `'X'` for checked,
* or `' '` (space) or `'\t'` (tab) for unchecked.
* @param {Function} done - Callback.
*/
function checkboxCharacterStyle(ast, file, preferred, done) {
var contents = file.toString();
var location = vfileLocation(file);
function checkboxCharacterStyle(ast, file, preferred) {
var contents = file.toString();
var location = vfileLocation(file);
if (preferred === 'consistent' || typeof preferred !== 'object') {
preferred = {};
}
if (preferred === 'consistent' || typeof preferred !== 'object') {
preferred = {};
}
if (!preferred.unchecked) {
preferred.unchecked = null;
}
if (!preferred.unchecked) {
preferred.unchecked = null;
}
if (!preferred.checked) {
preferred.checked = null;
}
if (!preferred.checked) {
preferred.checked = null;
}
if (
preferred.unchecked !== null &&
UNCHECKED[preferred.unchecked] !== true
) {
file.fail(
'Invalid unchecked checkbox marker `' +
preferred.unchecked +
'`: use either `\'\\t\'`, or `\' \'`'
);
}
if (
preferred.checked !== null &&
CHECKED[preferred.checked] !== true
) {
file.fail(
'Invalid checked checkbox marker `' +
preferred.checked +
'`: use either `\'x\'`, or `\'X\'`'
);
}
visit(ast, 'listItem', function (node) {
var type;
var initial;
var final;
var stop;
var value;
var style;
var character;
/* Exit early for items without checkbox. */
if (
preferred.unchecked !== null &&
UNCHECKED[preferred.unchecked] !== true
node.checked !== Boolean(node.checked) ||
position.generated(node)
) {
file.fail(
'Invalid unchecked checkbox marker `' +
preferred.unchecked +
'`: use either `\'\\t\'`, or `\' \'`'
);
return;
}
if (
preferred.checked !== null &&
CHECKED[preferred.checked] !== true
) {
file.fail(
'Invalid checked checkbox marker `' +
preferred.checked +
'`: use either `\'x\'`, or `\'X\'`'
);
type = node.checked ? 'checked' : 'unchecked';
initial = start(node).offset;
final = (node.children.length ? start(node.children[0]) : end(node)).offset;
/* For a checkbox to be parsed, it must be followed
* by a white space. */
value = contents.slice(initial, final).trimRight().slice(0, -1);
/* The checkbox character is behind a square
* bracket. */
character = value.charAt(value.length - 1);
style = preferred[type];
if (style === null) {
preferred[type] = character;
} else if (character !== style) {
stop = initial + value.length;
file.warn(
type.charAt(0).toUpperCase() + type.slice(1) +
' checkboxes should use `' + style + '` as a marker',
{
start: location.toPosition(stop - 1),
end: location.toPosition(stop)
}
);
}
visit(ast, 'listItem', function (node) {
var type;
var initial;
var final;
var stop;
var value;
var style;
var character;
/*
* Exit early for items without checkbox.
*/
if (
node.checked !== Boolean(node.checked) ||
position.generated(node)
) {
return;
}
type = node.checked ? 'checked' : 'unchecked';
initial = start(node).offset;
final = (node.children.length ? start(node.children[0]) : end(node)).offset;
/*
* For a checkbox to be parsed, it must be followed
* by a white space.
*/
value = contents.slice(initial, final).trimRight().slice(0, -1);
/*
* The checkbox character is behind a square
* bracket.
*/
character = value.charAt(value.length - 1);
style = preferred[type];
if (style === null) {
preferred[type] = character;
} else if (character !== style) {
stop = initial + value.length;
file.warn(
type.charAt(0).toUpperCase() + type.slice(1) +
' checkboxes should use `' + style + '` as a marker',
{
'start': location.toPosition(stop - 1),
'end': location.toPosition(stop)
}
);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = checkboxCharacterStyle;

View File

@ -21,20 +21,15 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = checkboxContentIndent;
/* Methods. */
var start = position.start;
var end = position.end;
@ -44,59 +39,44 @@ var end = position.end;
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function checkboxContentIndent(ast, file, preferred, done) {
var contents = file.toString();
var location = vfileLocation(file);
function checkboxContentIndent(ast, file) {
var contents = file.toString();
var location = vfileLocation(file);
visit(ast, 'listItem', function (node) {
var initial;
var final;
var value;
visit(ast, 'listItem', function (node) {
var initial;
var final;
var value;
/*
* Exit early for items without checkbox.
*/
/* Exit early for items without checkbox. */
if (
node.checked !== Boolean(node.checked) ||
position.generated(node)
) {
return;
}
if (
node.checked !== Boolean(node.checked) ||
position.generated(node)
) {
return;
}
initial = start(node).offset;
final = (node.children.length ? start(node.children[0]) : end(node)).offset;
initial = start(node).offset;
final = (node.children.length ? start(node.children[0]) : end(node)).offset;
while (/[^\S\n]/.test(contents.charAt(final))) {
final++;
}
while (/[^\S\n]/.test(contents.charAt(final))) {
final++;
}
/* For a checkbox to be parsed, it must be followed
* by a white space. */
value = contents.slice(initial, final);
/*
* For a checkbox to be parsed, it must be followed
* by a white space.
*/
value = value.slice(value.indexOf(']') + 1);
value = contents.slice(initial, final);
if (value.length === 1) {
return;
}
value = value.slice(value.indexOf(']') + 1);
if (value.length === 1) {
return;
}
file.warn('Checkboxes should be followed by a single character', {
'start': location.toPosition(final - value.length + 1),
'end': location.toPosition(final)
});
file.warn('Checkboxes should be followed by a single character', {
start: location.toPosition(final - value.length + 1),
end: location.toPosition(final)
});
done();
});
}
/*
* Expose.
*/
module.exports = checkboxContentIndent;

View File

@ -39,30 +39,22 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = codeBlockStyle;
/* Methods. */
var start = position.start;
var end = position.end;
/*
* Valid styles.
*/
/* Valid styles. */
var STYLES = {
'null': true,
'fenced': true,
'indented': true
null: true,
fenced: true,
indented: true
};
/**
@ -74,63 +66,55 @@ var STYLES = {
* code block style. Defaults to `'consistent'` when
* not a a string. Otherwise, should be one of
* `'fenced'` or `'indented'`.
* @param {Function} done - Callback.
*/
function codeBlockStyle(ast, file, preferred, done) {
var contents = file.toString();
function codeBlockStyle(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (STYLES[preferred] !== true) {
file.fail('Invalid code block style `' + preferred + '`: use either `\'consistent\'`, `\'fenced\'`, or `\'indented\'`');
done();
return;
if (STYLES[preferred] !== true) {
file.fail('Invalid code block style `' + preferred + '`: use either `\'consistent\'`, `\'fenced\'`, or `\'indented\'`');
return;
}
visit(ast, 'code', function (node) {
var current = check(node);
if (!current) {
return;
}
/**
* Get the style of `node`.
*
* @param {Node} node - Node.
* @return {string?} - `'fenced'`, `'indented'`, or
* `null`.
*/
function check(node) {
var initial = start(node).offset;
var final = end(node).offset;
if (!preferred) {
preferred = current;
} else if (preferred !== current) {
file.warn('Code blocks should be ' + preferred, node);
}
});
if (position.generated(node)) {
return null;
}
return;
if (
node.lang ||
/^\s*([~`])\1{2,}/.test(contents.slice(initial, final))
) {
return 'fenced';
}
/**
* Get the style of `node`.
*
* @param {Node} node - Node.
* @return {string?} - `'fenced'`, `'indented'`, or
* `null`.
*/
function check(node) {
var initial = start(node).offset;
var final = end(node).offset;
return 'indented';
if (position.generated(node)) {
return null;
}
visit(ast, 'code', function (node) {
var current = check(node);
if (
node.lang ||
/^\s*([~`])\1{2,}/.test(contents.slice(initial, final))
) {
return 'fenced';
}
if (!current) {
return;
}
if (!preferred) {
preferred = current;
} else if (preferred !== current) {
file.warn('Code blocks should be ' + preferred, node);
}
});
done();
return 'indented';
}
}
/*
* Expose.
*/
module.exports = codeBlockStyle;

View File

@ -15,19 +15,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Expressions.
*/
/* Expose. */
module.exports = definitionCase;
/* Expressions. */
var LABEL = /^\s*\[((?:\\[\s\S]|[^\[\]])+)\]/;
/**
@ -36,42 +31,34 @@ var LABEL = /^\s*\[((?:\\[\s\S]|[^\[\]])+)\]/;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function definitionCase(ast, file, preferred, done) {
var contents = file.toString();
function definitionCase(ast, file) {
var contents = file.toString();
/**
* Validate a node, either a normal definition or
* a footnote definition.
*
* @param {Node} node - Node.
*/
function validate(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var label;
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
if (position.generated(node)) {
return;
}
return;
label = contents.slice(start, end).match(LABEL)[1];
/**
* Validate a node, either a normal definition or
* a footnote definition.
*
* @param {Node} node - Node.
*/
function validate(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var label;
if (label !== label.toLowerCase()) {
file.warn('Do not use upper-case characters in definition labels', node);
}
if (position.generated(node)) {
return;
}
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
label = contents.slice(start, end).match(LABEL)[1];
done();
if (label !== label.toLowerCase()) {
file.warn('Do not use upper-case characters in definition labels', node);
}
}
}
/*
* Expose.
*/
module.exports = definitionCase;

View File

@ -15,19 +15,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Expressions.
*/
/* Expose. */
module.exports = definitionSpacing;
/* Expressions. */
var LABEL = /^\s*\[((?:\\[\s\S]|[^\[\]])+)\]/;
/**
@ -36,42 +31,34 @@ var LABEL = /^\s*\[((?:\\[\s\S]|[^\[\]])+)\]/;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function definitionSpacing(ast, file, preferred, done) {
var contents = file.toString();
function definitionSpacing(ast, file) {
var contents = file.toString();
/**
* Validate a node, either a normal definition or
* a footnote definition.
*
* @param {Node} node - Node.
*/
function validate(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var label;
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
if (position.generated(node)) {
return;
}
return;
label = contents.slice(start, end).match(LABEL)[1];
/**
* Validate a node, either a normal definition or
* a footnote definition.
*
* @param {Node} node - Node.
*/
function validate(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var label;
if (/[ \t\n]{2,}/.test(label)) {
file.warn('Do not use consecutive white-space in definition labels', node);
}
if (position.generated(node)) {
return;
}
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
label = contents.slice(start, end).match(LABEL)[1];
done();
if (/[ \t\n]{2,}/.test(label)) {
file.warn('Do not use consecutive white-space in definition labels', node);
}
}
}
/*
* Expose.
*/
module.exports = definitionSpacing;

View File

@ -26,21 +26,18 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Map of valid markers.
*/
/* Expose. */
module.exports = emphasisMarker;
/* Map of valid markers. */
var MARKERS = {
'*': true,
'_': true,
'null': true
'*': true,
'_': true,
'null': true
};
/**
@ -50,39 +47,28 @@ var MARKERS = {
* @param {File} file - Virtual file.
* @param {string?} [preferred='consistent'] - Preferred
* marker, either `'*'` or `'_'`, or `'consistent'`.
* @param {Function} done - Callback.
*/
function emphasisMarker(ast, file, preferred, done) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
function emphasisMarker(ast, file, preferred) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (MARKERS[preferred] !== true) {
file.fail('Invalid emphasis marker `' + preferred + '`: use either `\'consistent\'`, `\'*\'`, or `\'_\'`');
done();
if (MARKERS[preferred] !== true) {
file.fail('Invalid emphasis marker `' + preferred + '`: use either `\'consistent\'`, `\'*\'`, or `\'_\'`');
return;
}
return;
visit(ast, 'emphasis', function (node) {
var marker = file.toString().charAt(position.start(node).offset);
if (position.generated(node)) {
return;
}
visit(ast, 'emphasis', function (node) {
var marker = file.toString().charAt(position.start(node).offset);
if (position.generated(node)) {
return;
}
if (preferred) {
if (marker !== preferred) {
file.warn('Emphasis should use `' + preferred + '` as a marker', node);
}
} else {
preferred = marker;
}
});
done();
if (preferred) {
if (marker !== preferred) {
file.warn('Emphasis should use `' + preferred + '` as a marker', node);
}
} else {
preferred = marker;
}
});
}
/*
* Expose.
*/
module.exports = emphasisMarker;

View File

@ -42,19 +42,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = fencedCodeFlag;
/* Methods. */
var start = position.start;
var end = position.end;
@ -65,44 +60,35 @@ var end = position.end;
* @param {File} file - Virtual file.
* @param {Object|Array.<string>} [preferred] - List
* of flags deemed valid.
* @param {Function} done - Callback.
*/
function fencedCodeFlag(ast, file, preferred, done) {
var contents = file.toString();
var allowEmpty = false;
var flags = [];
function fencedCodeFlag(ast, file, preferred) {
var contents = file.toString();
var allowEmpty = false;
var flags = [];
if (typeof preferred === 'object' && !('length' in preferred)) {
allowEmpty = Boolean(preferred.allowEmpty);
if (typeof preferred === 'object' && !('length' in preferred)) {
allowEmpty = Boolean(preferred.allowEmpty);
preferred = preferred.flags;
preferred = preferred.flags;
}
if (typeof preferred === 'object' && 'length' in preferred) {
flags = String(preferred).split(',');
}
visit(ast, 'code', function (node) {
var value = contents.slice(start(node).offset, end(node).offset);
if (position.generated(node)) {
return;
}
if (typeof preferred === 'object' && 'length' in preferred) {
flags = String(preferred).split(',');
if (node.lang) {
if (flags.length && flags.indexOf(node.lang) === -1) {
file.warn('Invalid code-language flag', node);
}
} else if (/^ {0,3}([~`])\1{2,}/.test(value) && !allowEmpty) {
file.warn('Missing code-language flag', node);
}
visit(ast, 'code', function (node) {
var value = contents.slice(start(node).offset, end(node).offset);
if (position.generated(node)) {
return;
}
if (node.lang) {
if (flags.length && flags.indexOf(node.lang) === -1) {
file.warn('Invalid code-language flag', node);
}
} else if (/^\ {0,3}([~`])\1{2,}/.test(value) && !allowEmpty) {
file.warn('Missing code-language flag', node);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = fencedCodeFlag;

View File

@ -42,23 +42,18 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Map of valid markers.
*/
/* Expose. */
module.exports = fencedCodeMarker;
/* Map of valid markers. */
var MARKERS = {
'`': true,
'~': true,
'null': true
'`': true,
'~': true,
'null': true
};
/**
@ -68,51 +63,37 @@ var MARKERS = {
* @param {File} file - Virtual file.
* @param {string?} [preferred='consistent'] - Preferred
* marker, either `` '`' `` or `~`, or `'consistent'`.
* @param {Function} done - Callback.
*/
function fencedCodeMarker(ast, file, preferred, done) {
var contents = file.toString();
function fencedCodeMarker(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (MARKERS[preferred] !== true) {
file.fail('Invalid fenced code marker `' + preferred + '`: use either `\'consistent\'`, `` \'\`\' ``, or `\'~\'`');
done();
if (MARKERS[preferred] !== true) {
file.fail('Invalid fenced code marker `' + preferred + '`: use either `\'consistent\'`, `` \'`\' ``, or `\'~\'`');
return;
}
return;
visit(ast, 'code', function (node) {
var marker = contents.substr(position.start(node).offset, 4);
if (position.generated(node)) {
return;
}
visit(ast, 'code', function (node) {
var marker = contents.substr(position.start(node).offset, 4);
marker = marker.trimLeft().charAt(0);
if (position.generated(node)) {
return;
}
/* Ignore unfenced code blocks. */
if (MARKERS[marker] !== true) {
return;
}
marker = marker.trimLeft().charAt(0);
/*
* Ignore unfenced code blocks.
*/
if (MARKERS[marker] !== true) {
return;
}
if (preferred) {
if (marker !== preferred) {
file.warn('Fenced code should use ' + preferred + ' as a marker', node);
}
} else {
preferred = marker;
}
});
done();
if (preferred) {
if (marker !== preferred) {
file.warn('Fenced code should use ' + preferred + ' as a marker', node);
}
} else {
preferred = marker;
}
});
}
/*
* Expose.
*/
module.exports = fencedCodeMarker;

View File

@ -18,7 +18,8 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = fileExtension;
/**
* Check file extensions.
@ -27,22 +28,13 @@
* @param {File} file - Virtual file.
* @param {string?} [preferred='md'] - Expected file
* extension.
* @param {Function} done - Callback.
*/
function fileExtension(ast, file, preferred, done) {
var ext = file.extension;
function fileExtension(ast, file, preferred) {
var ext = file.extension;
preferred = typeof preferred === 'string' ? preferred : 'md';
preferred = typeof preferred === 'string' ? preferred : 'md';
if (ext !== '' && ext !== preferred) {
file.warn('Invalid extension: use `' + preferred + '`');
}
done();
if (ext !== '' && ext !== preferred) {
file.warn('Invalid extension: use `' + preferred + '`');
}
}
/*
* Expose.
*/
module.exports = fileExtension;

View File

@ -21,19 +21,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = finalDefinition;
/* Methods. */
var start = position.start;
/**
@ -42,37 +37,24 @@ var start = position.start;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function finalDefinition(ast, file, preferred, done) {
var last = null;
function finalDefinition(ast, file) {
var last = null;
visit(ast, function (node) {
var line = start(node).line;
visit(ast, function (node) {
var line = start(node).line;
/*
* Ignore generated nodes.
*/
/* Ignore generated nodes. */
if (node.type === 'root' || position.generated(node)) {
return;
}
if (node.type === 'root' || position.generated(node)) {
return;
}
if (node.type === 'definition') {
if (last !== null && last > line) {
file.warn('Move definitions to the end of the file (after the node at line `' + last + '`)', node);
}
} else if (last === null) {
last = line;
}
}, true);
done();
if (node.type === 'definition') {
if (last !== null && last > line) {
file.warn('Move definitions to the end of the file (after the node at line `' + last + '`)', node);
}
} else if (last === null) {
last = line;
}
}, true);
}
/*
* Expose.
*/
module.exports = finalDefinition;

View File

@ -12,7 +12,8 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = finalNewline;
/**
* Warn when the list-item marker style of unordered lists
@ -20,22 +21,12 @@
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function finalNewline(ast, file, preferred, done) {
var contents = file.toString();
var last = contents.length - 1;
function finalNewline(ast, file) {
var contents = file.toString();
var last = contents.length - 1;
if (last > 0 && contents.charAt(last) !== '\n') {
file.warn('Missing newline character at end of file');
}
done();
if (last > 0 && contents.charAt(last) !== '\n') {
file.warn('Missing newline character at end of file');
}
}
/*
* Expose.
*/
module.exports = finalNewline;

View File

@ -21,39 +21,32 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = firstHeadingLevel;
/**
* Warn when the first heading has a level other than a specified value.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {number?} [preferred=1] - First heading level.
* @param {Function} done - Callback.
*/
function firstHeadingLevel(ast, file, preferred, done) {
var style = preferred && preferred !== true ? preferred : 1;
function firstHeadingLevel(ast, file, preferred) {
var style = preferred && preferred !== true ? preferred : 1;
visit(ast, 'heading', function (node) {
if (position.generated(node)) {
return null;
}
visit(ast, 'heading', function (node) {
if (position.generated(node)) {
return;
}
if (node.depth !== style) {
file.warn('First heading level should be `' + style + '`', node);
}
if (node.depth !== style) {
file.warn('First heading level should be `' + style + '`', node);
}
return false;
});
done();
return false;
});
}
module.exports = firstHeadingLevel;

View File

@ -21,46 +21,36 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = hardBreakSpaces;
/**
* Warn when too many spaces are used to create a
* hard break.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function hardBreakSpaces(ast, file, preferred, done) {
var contents = file.toString();
function hardBreakSpaces(ast, file) {
var contents = file.toString();
visit(ast, 'break', function (node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var value;
visit(ast, 'break', function (node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var value;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
value = contents.slice(start, end).split('\n', 1)[0].replace(/\r$/, '');
value = contents.slice(start, end).split('\n', 1)[0].replace(/\r$/, '');
if (value.length > 2) {
file.warn('Use two spaces for hard line breaks', node);
}
});
done();
if (value.length > 2) {
file.warn('Use two spaces for hard line breaks', node);
}
});
}
/*
* Expose.
*/
module.exports = hardBreakSpaces;

View File

@ -19,15 +19,13 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = headingIncrement;
/**
* Warn when headings increment with more than 1 level at
* a time.
@ -36,31 +34,21 @@ var position = require('unist-util-position');
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function headingIncrement(ast, file, preferred, done) {
var prev = null;
function headingIncrement(ast, file) {
var prev = null;
visit(ast, 'heading', function (node) {
var depth = node.depth;
visit(ast, 'heading', function (node) {
var depth = node.depth;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (prev && depth > prev + 1) {
file.warn('Heading levels should increment by one level at a time', node);
}
if (prev && depth > prev + 1) {
file.warn('Heading levels should increment by one level at a time', node);
}
prev = depth;
});
done();
prev = depth;
});
}
/*
* Expose.
*/
module.exports = headingIncrement;

View File

@ -49,18 +49,15 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var style = require('mdast-util-heading-style');
var position = require('unist-util-position');
/*
* Types.
*/
/* Expose. */
module.exports = headingStyle;
/* Types. */
var TYPES = ['atx', 'atx-closed', 'setext'];
/**
@ -74,28 +71,20 @@ var TYPES = ['atx', 'atx-closed', 'setext'];
* detect the first used style.
* @param {Function} done - Callback.
*/
function headingStyle(ast, file, preferred, done) {
preferred = TYPES.indexOf(preferred) === -1 ? null : preferred;
function headingStyle(ast, file, preferred) {
preferred = TYPES.indexOf(preferred) === -1 ? null : preferred;
visit(ast, 'heading', function (node) {
if (position.generated(node)) {
return;
}
visit(ast, 'heading', function (node) {
if (position.generated(node)) {
return;
}
if (preferred) {
if (style(node, preferred) !== preferred) {
file.warn('Headings should use ' + preferred, node);
}
} else {
preferred = style(node, preferred);
}
});
done();
if (preferred) {
if (style(node, preferred) !== preferred) {
file.warn('Headings should use ' + preferred, node);
}
} else {
preferred = style(node, preferred);
}
});
}
/*
* Expose.
*/
module.exports = headingStyle;

View File

@ -8,68 +8,63 @@
'use strict';
/* eslint-env commonjs */
/*
* Expose.
*/
/* Expose. */
module.exports = {
'no-auto-link-without-protocol': require('./no-auto-link-without-protocol'),
'no-literal-urls': require('./no-literal-urls'),
'no-consecutive-blank-lines': require('./no-consecutive-blank-lines'),
'no-missing-blank-lines': require('./no-missing-blank-lines'),
'blockquote-indentation': require('./blockquote-indentation'),
'no-blockquote-without-caret': require('./no-blockquote-without-caret'),
'code-block-style': require('./code-block-style'),
'checkbox-content-indent': require('./checkbox-content-indent'),
'checkbox-character-style': require('./checkbox-character-style'),
'definition-case': require('./definition-case'),
'definition-spacing': require('./definition-spacing'),
'no-emphasis-as-heading': require('./no-emphasis-as-heading'),
'emphasis-marker': require('./emphasis-marker'),
'fenced-code-flag': require('./fenced-code-flag'),
'fenced-code-marker': require('./fenced-code-marker'),
'file-extension': require('./file-extension'),
'final-newline': require('./final-newline'),
'no-file-name-articles': require('./no-file-name-articles'),
'no-file-name-consecutive-dashes': require('./no-file-name-consecutive-dashes'),
'no-file-name-irregular-characters': require('./no-file-name-irregular-characters'),
'no-file-name-mixed-case': require('./no-file-name-mixed-case'),
'no-file-name-outer-dashes': require('./no-file-name-outer-dashes'),
'final-definition': require('./final-definition'),
'hard-break-spaces': require('./hard-break-spaces'),
'heading-increment': require('./heading-increment'),
'no-heading-content-indent': require('./no-heading-content-indent'),
'no-heading-indent': require('./no-heading-indent'),
'first-heading-level': require('./first-heading-level'),
'maximum-heading-length': require('./maximum-heading-length'),
'no-heading-punctuation': require('./no-heading-punctuation'),
'heading-style': require('./heading-style'),
'no-multiple-toplevel-headings': require('./no-multiple-toplevel-headings'),
'no-duplicate-headings': require('./no-duplicate-headings'),
'no-duplicate-definitions': require('./no-duplicate-definitions'),
'no-html': require('./no-html'),
'no-inline-padding': require('./no-inline-padding'),
'maximum-line-length': require('./maximum-line-length'),
'link-title-style': require('./link-title-style'),
'list-item-bullet-indent': require('./list-item-bullet-indent'),
'list-item-content-indent': require('./list-item-content-indent'),
'list-item-indent': require('./list-item-indent'),
'list-item-spacing': require('./list-item-spacing'),
'ordered-list-marker-style': require('./ordered-list-marker-style'),
'ordered-list-marker-value': require('./ordered-list-marker-value'),
'no-shortcut-reference-image': require('./no-shortcut-reference-image'),
'no-shortcut-reference-link': require('./no-shortcut-reference-link'),
'rule-style': require('./rule-style'),
'no-shell-dollars': require('./no-shell-dollars'),
'strong-marker': require('./strong-marker'),
'no-table-indentation': require('./no-table-indentation'),
'table-pipe-alignment': require('./table-pipe-alignment'),
'table-cell-padding': require('./table-cell-padding'),
'table-pipes': require('./table-pipes'),
'no-tabs': require('./no-tabs'),
'unordered-list-marker-style': require('./unordered-list-marker-style'),
'no-undefined-references': require('./no-undefined-references.js'),
'no-unused-definitions': require('./no-unused-definitions.js')
'no-auto-link-without-protocol': require('./no-auto-link-without-protocol'),
'no-literal-urls': require('./no-literal-urls'),
'no-consecutive-blank-lines': require('./no-consecutive-blank-lines'),
'no-missing-blank-lines': require('./no-missing-blank-lines'),
'blockquote-indentation': require('./blockquote-indentation'),
'no-blockquote-without-caret': require('./no-blockquote-without-caret'),
'code-block-style': require('./code-block-style'),
'checkbox-content-indent': require('./checkbox-content-indent'),
'checkbox-character-style': require('./checkbox-character-style'),
'definition-case': require('./definition-case'),
'definition-spacing': require('./definition-spacing'),
'no-emphasis-as-heading': require('./no-emphasis-as-heading'),
'emphasis-marker': require('./emphasis-marker'),
'fenced-code-flag': require('./fenced-code-flag'),
'fenced-code-marker': require('./fenced-code-marker'),
'file-extension': require('./file-extension'),
'final-newline': require('./final-newline'),
'no-file-name-articles': require('./no-file-name-articles'),
'no-file-name-consecutive-dashes': require('./no-file-name-consecutive-dashes'),
'no-file-name-irregular-characters': require('./no-file-name-irregular-characters'),
'no-file-name-mixed-case': require('./no-file-name-mixed-case'),
'no-file-name-outer-dashes': require('./no-file-name-outer-dashes'),
'final-definition': require('./final-definition'),
'hard-break-spaces': require('./hard-break-spaces'),
'heading-increment': require('./heading-increment'),
'no-heading-content-indent': require('./no-heading-content-indent'),
'no-heading-indent': require('./no-heading-indent'),
'first-heading-level': require('./first-heading-level'),
'maximum-heading-length': require('./maximum-heading-length'),
'no-heading-punctuation': require('./no-heading-punctuation'),
'heading-style': require('./heading-style'),
'no-multiple-toplevel-headings': require('./no-multiple-toplevel-headings'),
'no-duplicate-headings': require('./no-duplicate-headings'),
'no-duplicate-definitions': require('./no-duplicate-definitions'),
'no-html': require('./no-html'),
'no-inline-padding': require('./no-inline-padding'),
'maximum-line-length': require('./maximum-line-length'),
'link-title-style': require('./link-title-style'),
'list-item-bullet-indent': require('./list-item-bullet-indent'),
'list-item-content-indent': require('./list-item-content-indent'),
'list-item-indent': require('./list-item-indent'),
'list-item-spacing': require('./list-item-spacing'),
'ordered-list-marker-style': require('./ordered-list-marker-style'),
'ordered-list-marker-value': require('./ordered-list-marker-value'),
'no-shortcut-reference-image': require('./no-shortcut-reference-image'),
'no-shortcut-reference-link': require('./no-shortcut-reference-link'),
'rule-style': require('./rule-style'),
'no-shell-dollars': require('./no-shell-dollars'),
'strong-marker': require('./strong-marker'),
'no-table-indentation': require('./no-table-indentation'),
'table-pipe-alignment': require('./table-pipe-alignment'),
'table-cell-padding': require('./table-cell-padding'),
'table-pipes': require('./table-pipes'),
'no-tabs': require('./no-tabs'),
'unordered-list-marker-style': require('./unordered-list-marker-style'),
'no-undefined-references': require('./no-undefined-references.js'),
'no-unused-definitions': require('./no-unused-definitions.js')
};

View File

@ -33,33 +33,25 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Map of valid markers.
*/
var MARKERS = {
'"': true,
'\'': true,
')': true,
'null': true
};
/*
* Methods.
*/
/* Expose. */
module.exports = linkTitleStyle;
/* Methods. */
var end = position.end;
/* Map of valid markers. */
var MARKERS = {
'"': true,
'\'': true,
')': true,
'null': true
};
/**
* Warn for fenced code blocks without language flag.
*
@ -67,78 +59,66 @@ var end = position.end;
* @param {File} file - Virtual file.
* @param {string?} [preferred='consistent'] - Preferred
* marker, either `'"'`, `'\''`, `'()'`, or `'consistent'`.
* @param {Function} done - Callback.
*/
function linkTitleStyle(ast, file, preferred, done) {
var contents = file.toString();
var location = vfileLocation(file);
function linkTitleStyle(ast, file, preferred) {
var contents = file.toString();
var location = vfileLocation(file);
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (preferred === '()' || preferred === '(') {
preferred = ')';
if (preferred === '()' || preferred === '(') {
preferred = ')';
}
if (MARKERS[preferred] !== true) {
file.fail('Invalid link title style marker `' + preferred + '`: use either `\'consistent\'`, `\'"\'`, `\'\\\'\'`, or `\'()\'`');
return;
}
visit(ast, 'link', validate);
visit(ast, 'image', validate);
visit(ast, 'definition', validate);
return;
/**
* Validate a single node.
*
* @param {Node} node - Node.
*/
function validate(node) {
var last = end(node).offset - 1;
var character;
var pos;
if (position.generated(node)) {
return;
}
if (MARKERS[preferred] !== true) {
file.fail('Invalid link title style marker `' + preferred + '`: use either `\'consistent\'`, `\'"\'`, `\'\\\'\'`, or `\'()\'`');
done();
return;
if (node.type !== 'definition') {
last--;
}
/**
* Validate a single node.
*
* @param {Node} node - Node.
*/
function validate(node) {
var last = end(node).offset - 1;
var character;
var pos;
while (last) {
character = contents.charAt(last);
if (position.generated(node)) {
return;
}
if (node.type !== 'definition') {
last--;
}
while (last) {
character = contents.charAt(last);
if (/\s/.test(character)) {
last--;
} else {
break;
}
}
/*
* Not a title.
*/
if (!(character in MARKERS)) {
return;
}
if (!preferred) {
preferred = character;
} else if (preferred !== character) {
pos = location.toPosition(last + 1);
file.warn('Titles should use `' + (preferred === ')' ? '()' : preferred) + '` as a quote', pos);
}
if (/\s/.test(character)) {
last--;
} else {
break;
}
}
visit(ast, 'link', validate);
visit(ast, 'image', validate);
visit(ast, 'definition', validate);
/* Not a title. */
if (!(character in MARKERS)) {
return;
}
done();
if (!preferred) {
preferred = character;
} else if (preferred !== character) {
pos = location.toPosition(last + 1);
file.warn('Titles should use `' + (preferred === ')' ? '()' : preferred) + '` as a quote', pos);
}
}
}
/*
* Expose.
*/
module.exports = linkTitleStyle;

View File

@ -17,20 +17,15 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var plural = require('plur');
/*
* Methods.
*/
/* Expose. */
module.exports = listItemBulletIndent;
/* Methods. */
var start = position.start;
/**
@ -38,43 +33,33 @@ var start = position.start;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function listItemBulletIndent(ast, file, preferred, done) {
var contents = file.toString();
function listItemBulletIndent(ast, file) {
var contents = file.toString();
visit(ast, 'list', function (node) {
var items = node.children;
visit(ast, 'list', function (node) {
var items = node.children;
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var indent;
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var indent;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
indent = contents.slice(initial, final).match(/^\s*/)[0].length;
indent = contents.slice(initial, final).match(/^\s*/)[0].length;
if (indent !== 0) {
initial = start(head);
if (indent !== 0) {
initial = start(head);
file.warn('Incorrect indentation before bullet: remove ' + indent + ' ' + plural('space', indent), {
'line': initial.line,
'column': initial.column - indent
});
}
file.warn('Incorrect indentation before bullet: remove ' + indent + ' ' + plural('space', indent), {
line: initial.line,
column: initial.column - indent
});
}
});
done();
});
}
/*
* Expose.
*/
module.exports = listItemBulletIndent;

View File

@ -19,20 +19,15 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var plural = require('plur');
/*
* Methods.
*/
/* Expose. */
module.exports = listItemContentIndent;
/* Methods. */
var start = position.start;
/**
@ -41,81 +36,62 @@ var start = position.start;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function listItemContentIndent(ast, file, preferred, done) {
var contents = file.toString();
function listItemContentIndent(ast, file) {
var contents = file.toString();
visit(ast, 'listItem', function (node) {
var style;
visit(ast, 'listItem', function (node) {
var style;
node.children.forEach(function (item, index) {
var begin = start(item);
var column = begin.column;
var char;
var diff;
var word;
node.children.forEach(function (item, index) {
var begin = start(item);
var column = begin.column;
var char;
var diff;
var word;
if (position.generated(item)) {
return;
}
if (position.generated(item)) {
return;
}
/*
* Get indentation for the first child.
* Only the first item can have a checkbox,
* so here we remove that from the column.
*/
/* Get indentation for the first child.
* Only the first item can have a checkbox,
* so here we remove that from the column. */
if (index === 0) {
/* If theres a checkbox before the content,
* look backwards to find the start of that
* checkbox. */
if (Boolean(node.checked) === node.checked) {
char = begin.offset - 1;
if (index === 0) {
/*
* If theres a checkbox before the content,
* look backwards to find the start of that
* checkbox.
*/
while (contents.charAt(char) !== '[') {
char--;
}
if (Boolean(node.checked) === node.checked) {
char = begin.offset - 1;
column -= begin.offset - char;
}
while (contents.charAt(char) !== '[') {
char--;
}
style = column;
column -= begin.offset - char;
}
return;
}
style = column;
/* Warn for violating children. */
if (column !== style) {
diff = style - column;
word = diff > 0 ? 'add' : 'remove';
return;
}
diff = Math.abs(diff);
/*
* Warn for violating children.
*/
if (column !== style) {
diff = style - column;
word = diff > 0 ? 'add' : 'remove';
diff = Math.abs(diff);
file.warn(
'Dont use mixed indentation for children, ' + word +
' ' + diff + ' ' + plural('space', diff),
{
'line': start(item).line,
'column': column
}
);
}
});
file.warn(
'Dont use mixed indentation for children, ' + word +
' ' + diff + ' ' + plural('space', diff),
{
line: start(item).line,
column: column
}
);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = listItemContentIndent;

View File

@ -44,28 +44,22 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var plural = require('plur');
/*
* Methods.
*/
/* Expose. */
module.exports = listItemIndent;
/* Methods. */
var start = position.start;
/*
* Styles.
*/
/* Styles. */
var STYLES = {
'tab-size': true,
'mixed': true,
'space': true
'tab-size': true,
'mixed': true,
'space': true
};
/**
@ -79,75 +73,62 @@ var STYLES = {
* to the first.
* @param {Function} done - Callback.
*/
function listItemIndent(ast, file, preferred, done) {
var contents = file.toString();
function listItemIndent(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' ? 'tab-size' : preferred;
preferred = typeof preferred === 'string' ? preferred : 'tab-size';
if (STYLES[preferred] !== true) {
file.fail('Invalid list-item indent style `' + preferred + '`: use either `\'tab-size\'`, `\'space\'`, or `\'mixed\'`');
done();
if (STYLES[preferred] !== true) {
file.fail('Invalid list-item indent style `' + preferred + '`: use either `\'tab-size\'`, `\'space\'`, or `\'mixed\'`');
return;
}
return;
visit(ast, 'list', function (node) {
var items = node.children;
if (position.generated(node)) {
return;
}
visit(ast, 'list', function (node) {
var items = node.children;
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var bulletSize;
var tab;
var marker;
var shouldBe;
var diff;
var word;
if (position.generated(node)) {
return;
}
marker = contents.slice(initial, final);
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var bulletSize;
var tab;
var marker;
var shouldBe;
var diff;
var word;
/* Support checkboxes. */
marker = marker.replace(/\[[x ]?\]\s*$/i, '');
marker = contents.slice(initial, final);
bulletSize = marker.trimRight().length;
tab = Math.ceil(bulletSize / 4) * 4;
/*
* Support checkboxes.
*/
if (preferred === 'tab-size') {
shouldBe = tab;
} else if (preferred === 'space') {
shouldBe = bulletSize + 1;
} else {
shouldBe = node.loose ? tab : bulletSize + 1;
}
marker = marker.replace(/\[[x ]?\]\s*$/i, '');
if (marker.length !== shouldBe) {
diff = shouldBe - marker.length;
word = diff > 0 ? 'add' : 'remove';
bulletSize = marker.trimRight().length;
tab = Math.ceil(bulletSize / 4) * 4;
diff = Math.abs(diff);
if (preferred === 'tab-size') {
shouldBe = tab;
} else if (preferred === 'space') {
shouldBe = bulletSize + 1;
} else {
shouldBe = node.loose ? tab : bulletSize + 1;
}
if (marker.length !== shouldBe) {
diff = shouldBe - marker.length;
word = diff > 0 ? 'add' : 'remove';
diff = Math.abs(diff);
file.warn(
'Incorrect list-item indent: ' + word +
' ' + diff + ' ' + plural('space', diff),
start(head)
);
}
});
file.warn(
'Incorrect list-item indent: ' + word +
' ' + diff + ' ' + plural('space', diff),
start(head)
);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = listItemIndent;

View File

@ -36,19 +36,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = listItemSpacing;
/* Methods. */
var start = position.start;
var end = position.end;
@ -58,71 +53,55 @@ var end = position.end;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function listItemSpacing(ast, file, preferred, done) {
visit(ast, 'list', function (node) {
var items = node.children;
var isTightList = true;
var indent = start(node).column;
var type;
function listItemSpacing(ast, file) {
visit(ast, 'list', function (node) {
var items = node.children;
var isTightList = true;
var indent = start(node).column;
var type;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
items.forEach(function (item) {
var content = item.children;
var head = content[0];
var tail = content[content.length - 1];
var isLoose = (end(tail).line - start(head).line) > 0;
items.forEach(function (item) {
var content = item.children;
var head = content[0];
var tail = content[content.length - 1];
var isLoose = (end(tail).line - start(head).line) > 0;
if (isLoose) {
isTightList = false;
}
});
type = isTightList ? 'tight' : 'loose';
items.forEach(function (item, index) {
var next = items[index + 1];
var isTight = end(item).column > indent;
/*
* Ignore last.
*/
if (!next) {
return;
}
/*
* Check if the list item's state does (not)
* match the list's state.
*/
if (isTight !== isTightList) {
if (type === 'loose') {
file.warn('Missing new line after list item', {
'start': end(item),
'end': start(next)
});
} else {
file.warn('Extraneous new line after list item', {
'start': end(item),
'end': start(next)
});
}
}
});
if (isLoose) {
isTightList = false;
}
});
done();
type = isTightList ? 'tight' : 'loose';
items.forEach(function (item, index) {
var next = items[index + 1];
var isTight = end(item).column > indent;
/* Ignore last. */
if (!next) {
return;
}
/* Check if the list item's state does (not)
* match the list's state. */
if (isTight !== isTightList) {
if (type === 'loose') {
file.warn('Missing new line after list item', {
start: end(item),
end: start(next)
});
} else {
file.warn('Extraneous new line after list item', {
start: end(item),
end: start(next)
});
}
}
});
});
}
/*
* Expose.
*/
module.exports = listItemSpacing;

View File

@ -20,16 +20,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
var position = require('unist-util-position');
/* Expose. */
module.exports = maximumHeadingLength;
/**
* Warn when headings are too long.
*
@ -39,24 +37,16 @@ var position = require('unist-util-position');
* length.
* @param {Function} done - Callback.
*/
function maximumHeadingLength(ast, file, preferred, done) {
preferred = isNaN(preferred) || typeof preferred !== 'number' ? 60 : preferred;
function maximumHeadingLength(ast, file, preferred) {
preferred = isNaN(preferred) || typeof preferred !== 'number' ? 60 : preferred;
visit(ast, 'heading', function (node) {
if (position.generated(node)) {
return;
}
visit(ast, 'heading', function (node) {
if (position.generated(node)) {
return;
}
if (toString(node).length > preferred) {
file.warn('Use headings shorter than `' + preferred + '`', node);
}
});
done();
if (toString(node).length > preferred) {
file.warn('Use headings shorter than `' + preferred + '`', node);
}
});
}
/*
* Expose.
*/
module.exports = maximumHeadingLength;

View File

@ -32,22 +32,119 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = maximumLineLength;
/* Methods. */
var start = position.start;
var end = position.end;
/**
* Warn when lines are too long. This rule is forgiving
* about lines which cannot be wrapped, such as code,
* tables, and headings, or links at the enc of a line.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {number?} [preferred=80] - Maximum line length.
*/
function maximumLineLength(ast, file, preferred) {
var style = preferred && preferred !== true ? preferred : 80;
var content = file.toString();
var matrix = content.split('\n');
var index = -1;
var length = matrix.length;
var lineLength;
/* Next, white list nodes which cannot be wrapped. */
visit(ast, function (node) {
var applicable = isIgnored(node);
var initial = applicable && start(node).line;
var final = applicable && end(node).line;
if (!applicable || position.generated(node)) {
return;
}
whitelist(initial - 1, final);
});
visit(ast, 'link', validateLink);
visit(ast, 'image', validateLink);
/* Iterate over every line, and warn for
* violating lines. */
while (++index < length) {
lineLength = matrix[index].length;
if (lineLength > style) {
file.warn('Line must be at most ' + style + ' characters', {
line: index + 1,
column: lineLength + 1
});
}
}
return;
/**
* Whitelist from `initial` to `final`, zero-based.
*
* @param {number} initial - Start.
* @param {number} final - End.
*/
function whitelist(initial, final) {
initial--;
while (++initial < final) {
matrix[initial] = '';
}
}
/**
* Finally, whitelist URLs, but only if they occur at
* or after the wrap. However, when they do, and
* theres white-space after it, they are not
* whitelisted.
*
* @param {Node} node - Node.
* @param {number} pos - Position of `node` in `parent`.
* @param {Node} parent - Parent of `node`.
*/
function validateLink(node, pos, parent) {
var next = parent.children[pos + 1];
var initial = start(node);
var final = end(node);
/* Nothing to whitelist when generated. */
if (position.generated(node)) {
return;
}
/* No whitelisting when starting after the border,
* or ending before it. */
if (initial.column > style || final.column < style) {
return;
}
/* No whitelisting when theres white-space after
* the link. */
if (
next &&
start(next).line === initial.line &&
(!next.value || /^(.+?[ \t].+?)/.test(next.value))
) {
return;
}
whitelist(initial.line - 1, final.line);
}
}
/**
* Check if `node` is applicable, as in, if it should be
* ignored.
@ -57,132 +154,8 @@ var end = position.end;
* ignored.
*/
function isIgnored(node) {
return node.type === 'heading' ||
node.type === 'table' ||
node.type === 'code' ||
node.type === 'definition';
return node.type === 'heading' ||
node.type === 'table' ||
node.type === 'code' ||
node.type === 'definition';
}
/**
* Warn when lines are too long. This rule is forgiving
* about lines which cannot be wrapped, such as code,
* tables, and headings, or links at the enc of a line.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {number?} [preferred=80] - Maximum line length.
* @param {Function} done - Callback.
*/
function maximumLineLength(ast, file, preferred, done) {
var style = preferred && preferred !== true ? preferred : 80;
var content = file.toString();
var matrix = content.split('\n');
var index = -1;
var length = matrix.length;
var lineLength;
/**
* Whitelist from `initial` to `final`, zero-based.
*
* @param {number} initial - Start.
* @param {number} final - End.
*/
function whitelist(initial, final) {
initial--;
while (++initial < final) {
matrix[initial] = '';
}
}
/*
* Next, white list nodes which cannot be wrapped.
*/
visit(ast, function (node) {
var applicable = isIgnored(node);
var initial = applicable && start(node).line;
var final = applicable && end(node).line;
if (!applicable || position.generated(node)) {
return;
}
whitelist(initial - 1, final);
});
/**
* Finally, whitelist URLs, but only if they occur at
* or after the wrap. However, when they do, and
* theres white-space after it, they are not
* whitelisted.
*
* @param {Node} node - Node.
* @param {number} pos - Position of `node` in `parent`.
* @param {Node} parent - Parent of `node`.
*/
function validateLink(node, pos, parent) {
var next = parent.children[pos + 1];
var initial = start(node);
var final = end(node);
/*
* Nothing to whitelist when generated.
*/
if (position.generated(node)) {
return;
}
/*
* No whitelisting when starting after the border,
* or ending before it.
*/
if (initial.column > style || final.column < style) {
return;
}
/*
* No whitelisting when theres white-space after
* the link.
*/
if (
next &&
start(next).line === initial.line &&
(!next.value || /^(.+?[ \t].+?)/.test(next.value))
) {
return;
}
whitelist(initial.line - 1, final.line);
}
visit(ast, 'link', validateLink);
visit(ast, 'image', validateLink);
/*
* Iterate over every line, and warn for
* violating lines.
*/
while (++index < length) {
lineLength = matrix[index].length;
if (lineLength > style) {
file.warn('Line must be at most ' + style + ' characters', {
'line': index + 1,
'column': lineLength + 1
});
}
}
done();
}
/*
* Expose.
*/
module.exports = maximumLineLength;

View File

@ -19,18 +19,15 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = noAutoLinkWithoutProtocol;
/* Methods. */
var start = position.start;
var end = position.end;
@ -43,6 +40,29 @@ var end = position.end;
var PROTOCOL = /^[a-z][a-z+.-]+:\/?/i;
/**
* Warn for angle-bracketed links without protocol.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
*/
function noAutoLinkWithoutProtocol(ast, file) {
visit(ast, 'link', function (node) {
var head = start(node.children[0]).column;
var tail = end(node.children[node.children.length - 1]).column;
var initial = start(node).column;
var final = end(node).column;
if (position.generated(node)) {
return;
}
if (initial === head - 1 && final === tail + 1 && !hasProtocol(node)) {
file.warn('All automatic links must start with a protocol', node);
}
});
}
/**
* Assert `node`s reference starts with a protocol.
*
@ -50,38 +70,5 @@ var PROTOCOL = /^[a-z][a-z+.-]+:\/?/i;
* @return {boolean} - Whether `node` has a protocol.
*/
function hasProtocol(node) {
return PROTOCOL.test(toString(node));
return PROTOCOL.test(toString(node));
}
/**
* Warn for angle-bracketed links without protocol.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noAutoLinkWithoutProtocol(ast, file, preferred, done) {
visit(ast, 'link', function (node) {
var head = start(node.children[0]).column;
var tail = end(node.children[node.children.length - 1]).column;
var initial = start(node).column;
var final = end(node).column;
if (position.generated(node)) {
return;
}
if (initial === head - 1 && final === tail + 1 && !hasProtocol(node)) {
file.warn('All automatic links must start with a protocol', node);
}
});
done();
}
/*
* Expose.
*/
module.exports = noAutoLinkWithoutProtocol;

View File

@ -19,71 +19,59 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noBlockquoteWithoutCaret;
/**
* Warn when blank lines without carets are found in a
* blockquote.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noBlockquoteWithoutCaret(ast, file, preferred, done) {
var contents = file.toString();
var location = vfileLocation(file);
var last = contents.length;
function noBlockquoteWithoutCaret(ast, file) {
var contents = file.toString();
var location = vfileLocation(file);
var last = contents.length;
visit(ast, 'blockquote', function (node) {
var start = position.start(node).line;
var indent = node.position && node.position.indent;
visit(ast, 'blockquote', function (node) {
var start = position.start(node).line;
var indent = node.position && node.position.indent;
if (position.generated(node) || !indent || !indent.length) {
return;
if (position.generated(node) || !indent || !indent.length) {
return;
}
indent.forEach(function (column, n) {
var character;
var line = start + n + 1;
var offset = location.toOffset({
line: line,
column: column
}) - 1;
while (++offset < last) {
character = contents.charAt(offset);
if (character === '>') {
return;
}
indent.forEach(function (column, n) {
var character;
var line = start + n + 1;
var offset = location.toOffset({
'line': line,
'column': column
}) - 1;
/* istanbul ignore else - just for safety */
if (character !== ' ' && character !== '\t') {
break;
}
}
while (++offset < last) {
character = contents.charAt(offset);
if (character === '>') {
return;
}
/* istanbul ignore else - just for safety */
if (character !== ' ' && character !== '\t') {
break;
}
}
file.warn('Missing caret in blockquote', {
'line': line,
'column': column
});
});
file.warn('Missing caret in blockquote', {
line: line,
column: column
});
});
done();
});
}
/*
* Expose.
*/
module.exports = noBlockquoteWithoutCaret;

View File

@ -23,18 +23,15 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var plural = require('plur');
/*
* Constants.
*/
/* Expose. */
module.exports = noConsecutiveBlankLines;
/* Constants. */
var MAX = 2;
/**
@ -44,93 +41,69 @@ var MAX = 2;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noConsecutiveBlankLines(ast, file, preferred, done) {
/**
* Compare the difference between `start` and `end`,
* and warn when that difference exceeds `max`.
*
* @param {Position} start - Initial.
* @param {Position} end - Final.
* @param {number} max - Threshold.
*/
function compare(start, end, max) {
var diff = end.line - start.line;
var word = diff > 0 ? 'before' : 'after';
function noConsecutiveBlankLines(ast, file) {
visit(ast, function (node) {
var children = node.children;
var head = children && children[0];
var tail = children && children[children.length - 1];
diff = Math.abs(diff) - max;
if (diff > 0) {
file.warn('Remove ' + diff + ' ' + plural('line', diff) + ' ' + word + ' node', end);
}
if (position.generated(node)) {
return;
}
visit(ast, function (node) {
var children = node.children;
var head = children && children[0];
var tail = children && children[children.length - 1];
if (head && !position.generated(head)) {
/* Compare parent and first child. */
compare(position.start(node), position.start(head), 0);
if (position.generated(node)) {
return;
/* Compare between each child. */
children.forEach(function (child, index) {
var prev = children[index - 1];
var max = MAX;
if (
!prev ||
position.generated(prev) ||
position.generated(child)
) {
return;
}
if (head && !position.generated(head)) {
/*
* Compare parent and first child.
*/
compare(position.start(node), position.start(head), 0);
/*
* Compare between each child.
*/
children.forEach(function (child, index) {
var prev = children[index - 1];
var max = MAX;
if (
!prev ||
position.generated(prev) ||
position.generated(child)
) {
return;
}
if (
(
prev.type === 'list' &&
child.type === 'list'
) ||
(
child.type === 'code' &&
prev.type === 'list' &&
!child.lang
)
) {
max++;
}
compare(position.end(prev), position.start(child), max);
});
/*
* Compare parent and last child.
*/
if (tail !== head && !position.generated(tail)) {
compare(position.end(node), position.end(tail), 1);
}
if (
(prev.type === 'list' && child.type === 'list') ||
(child.type === 'code' && prev.type === 'list' && !child.lang)
) {
max++;
}
});
done();
compare(position.end(prev), position.start(child), max);
});
/* Compare parent and last child. */
if (tail !== head && !position.generated(tail)) {
compare(position.end(node), position.end(tail), 1);
}
}
});
return;
/**
* Compare the difference between `start` and `end`,
* and warn when that difference exceeds `max`.
*
* @param {Position} start - Initial.
* @param {Position} end - Final.
* @param {number} max - Threshold.
*/
function compare(start, end, max) {
var diff = end.line - start.line;
var word = diff > 0 ? 'before' : 'after';
diff = Math.abs(diff) - max;
if (diff > 0) {
file.warn('Remove ' + diff + ' ' + plural('line', diff) + ' ' + word + ' node', end);
}
}
}
/*
* Expose.
*/
module.exports = noConsecutiveBlankLines;

View File

@ -17,15 +17,13 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var position = require('unist-util-position');
var visit = require('unist-util-visit');
/* Expose. */
module.exports = noDuplicateDefinitions;
/**
* Warn when definitions with equal content are found.
*
@ -33,46 +31,38 @@ var visit = require('unist-util-visit');
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noDuplicateDefinitions(ast, file, preferred, done) {
var map = {};
function noDuplicateDefinitions(ast, file) {
var map = {};
/**
* Check `node`.
*
* @param {Node} node - Node.
*/
function validate(node) {
var duplicate = map[node.identifier];
var pos;
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
if (position.generated(node)) {
return;
}
return;
if (duplicate && duplicate.type) {
pos = position.start(duplicate);
/**
* Check `node`.
*
* @param {Node} node - Node.
*/
function validate(node) {
var duplicate = map[node.identifier];
var pos;
file.warn(
'Do not use definitions with the same identifier (' +
pos.line + ':' + pos.column + ')',
node
);
}
map[node.identifier] = node;
if (position.generated(node)) {
return;
}
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
if (duplicate && duplicate.type) {
pos = position.start(duplicate);
done();
file.warn(
'Do not use definitions with the same identifier (' +
pos.line + ':' + pos.column + ')',
node
);
}
map[node.identifier] = node;
}
}
/*
* Expose.
*/
module.exports = noDuplicateDefinitions;

View File

@ -23,14 +23,14 @@
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var position = require('unist-util-position');
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
/* Expose. */
module.exports = noDuplicateHeadings;
/**
* Warn when headings with equal content are found.
*
@ -38,39 +38,29 @@ var toString = require('mdast-util-to-string');
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noDuplicateHeadings(ast, file, preferred, done) {
var map = {};
function noDuplicateHeadings(ast, file) {
var map = {};
visit(ast, 'heading', function (node) {
var value = toString(node).toUpperCase();
var duplicate = map[value];
var pos;
visit(ast, 'heading', function (node) {
var value = toString(node).toUpperCase();
var duplicate = map[value];
var pos;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (duplicate && duplicate.type === 'heading') {
pos = position.start(duplicate);
if (duplicate && duplicate.type === 'heading') {
pos = position.start(duplicate);
file.warn(
'Do not use headings with similar content (' +
pos.line + ':' + pos.column + ')',
node
);
}
file.warn(
'Do not use headings with similar content (' +
pos.line + ':' + pos.column + ')',
node
);
}
map[value] = node;
});
done();
map[value] = node;
});
}
/*
* Expose.
*/
module.exports = noDuplicateHeadings;

View File

@ -20,51 +20,39 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noEmphasisAsHeading;
/**
* Warn when a section (a new paragraph) is introduced
* by emphasis (or strong) and a colon.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noEmphasisAsHeading(ast, file, preferred, done) {
visit(ast, 'paragraph', function (node, index, parent) {
var children = node.children;
var child = children[0];
var prev = parent.children[index - 1];
var next = parent.children[index + 1];
function noEmphasisAsHeading(ast, file) {
visit(ast, 'paragraph', function (node, index, parent) {
var children = node.children;
var child = children[0];
var prev = parent.children[index - 1];
var next = parent.children[index + 1];
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (
(!prev || prev.type !== 'heading') &&
next &&
next.type === 'paragraph' &&
children.length === 1 &&
(child.type === 'emphasis' || child.type === 'strong')
) {
file.warn('Dont use emphasis to introduce a section, use a heading', node);
}
});
done();
if (
(!prev || prev.type !== 'heading') &&
next &&
next.type === 'paragraph' &&
children.length === 1 &&
(child.type === 'emphasis' || child.type === 'strong')
) {
file.warn('Dont use emphasis to introduce a section, use a heading', node);
}
});
}
/*
* Expose.
*/
module.exports = noEmphasisAsHeading;

View File

@ -12,28 +12,19 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = noFileNameArticles;
/**
* Warn when file name start with an article.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noFileNameArticles(ast, file, preferred, done) {
var match = file.filename && file.filename.match(/^(the|an?)\b/i);
function noFileNameArticles(ast, file) {
var match = file.filename && file.filename.match(/^(the|an?)\b/i);
if (match) {
file.warn('Do not start file names with `' + match[0] + '`');
}
done();
if (match) {
file.warn('Do not start file names with `' + match[0] + '`');
}
}
/*
* Expose.
*/
module.exports = noFileNameArticles;

View File

@ -12,26 +12,17 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = noFileNameConsecutiveDashes;
/**
* Warn when file names contain consecutive dashes.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noFileNameConsecutiveDashes(ast, file, preferred, done) {
if (file.filename && /-{2,}/.test(file.filename)) {
file.warn('Do not use consecutive dashes in a file name');
}
done();
function noFileNameConsecutiveDashes(ast, file) {
if (file.filename && /-{2,}/.test(file.filename)) {
file.warn('Do not use consecutive dashes in a file name');
}
}
/*
* Expose.
*/
module.exports = noFileNameConsecutiveDashes;

View File

@ -21,7 +21,8 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = noFileNameIrregularCharacters;
/**
* Warn when file names contain characters other than
@ -29,28 +30,22 @@
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
* @param {string|RegExp} preferred - RegExp, or string of
* characters (in which case its wrapped in
* `new RegExp('[^' + preferred + ']')`), that matches
* characters which should not be allowed.
*/
function noFileNameIrregularCharacters(ast, file, preferred, done) {
var expression = preferred || /[^\\.a-zA-Z0-9-]/;
var match;
function noFileNameIrregularCharacters(ast, file, preferred) {
var expression = preferred || /[^\\.a-zA-Z0-9-]/;
var match;
if (typeof expression === 'string') {
expression = new RegExp('[^' + expression + ']');
}
if (typeof expression === 'string') {
expression = new RegExp('[^' + expression + ']');
}
match = file.filename && file.filename.match(expression);
match = file.filename && file.filename.match(expression);
if (match) {
file.warn('Do not use `' + match[0] + '` in a file name');
}
done();
if (match) {
file.warn('Do not use `' + match[0] + '` in a file name');
}
}
/*
* Expose.
*/
module.exports = noFileNameIrregularCharacters;

View File

@ -13,7 +13,8 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = noFileNameMixedCase;
/**
* Warn when a file name uses mixed case: both upper- and
@ -21,21 +22,11 @@
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noFileNameMixedCase(ast, file, preferred, done) {
var name = file.filename;
function noFileNameMixedCase(ast, file) {
var name = file.filename;
if (name && !(name === name.toLowerCase() || name === name.toUpperCase())) {
file.warn('Do not mix casing in file names');
}
done();
if (name && !(name === name.toLowerCase() || name === name.toUpperCase())) {
file.warn('Do not mix casing in file names');
}
}
/*
* Expose.
*/
module.exports = noFileNameMixedCase;

View File

@ -12,26 +12,17 @@
'use strict';
/* eslint-env commonjs */
/* Expose. */
module.exports = noFileNameOuterDashes;
/**
* Warn when file names contain initial or final dashes.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noFileNameOuterDashes(ast, file, preferred, done) {
if (file.filename && /^-|-$/.test(file.filename)) {
file.warn('Do not use initial or final dashes in a file name');
}
done();
function noFileNameOuterDashes(ast, file) {
if (file.filename && /^-|-$/.test(file.filename)) {
file.warn('Do not use initial or final dashes in a file name');
}
}
/*
* Expose.
*/
module.exports = noFileNameOuterDashes;

View File

@ -24,21 +24,16 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var style = require('mdast-util-heading-style');
var plural = require('plur');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = noHeadingContentIndent;
/* Methods. */
var start = position.start;
var end = position.end;
@ -49,93 +44,83 @@ var end = position.end;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noHeadingContentIndent(ast, file, preferred, done) {
var contents = file.toString();
function noHeadingContentIndent(ast, file) {
var contents = file.toString();
visit(ast, 'heading', function (node) {
var depth = node.depth;
var children = node.children;
var type = style(node, 'atx');
var head;
var initial;
var final;
var diff;
var word;
var index;
var char;
visit(ast, 'heading', function (node) {
var depth = node.depth;
var children = node.children;
var type = style(node, 'atx');
var head;
var initial;
var final;
var diff;
var word;
var index;
var char;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (type === 'atx' || type === 'atx-closed') {
initial = start(node);
index = initial.offset;
char = contents.charAt(index);
if (type === 'atx' || type === 'atx-closed') {
initial = start(node);
index = initial.offset;
char = contents.charAt(index);
while (char && char !== '#') {
index++;
char = contents.charAt(index);
}
while (char && char !== '#') {
index++;
char = contents.charAt(index);
}
/* CR/LF bug: wooorm/remark#195. */
if (!char) {
return;
}
/* CR/LF bug: wooorm/remark#195. */
if (!char) {
return;
}
index = depth + (index - initial.offset);
head = start(children[0]).column;
index = depth + (index - initial.offset);
head = start(children[0]).column;
/*
* Ignore empty headings.
*/
/*
* Ignore empty headings.
*/
if (!head) {
return;
}
if (!head) {
return;
}
diff = head - initial.column - 1 - index;
diff = head - initial.column - 1 - index;
if (diff) {
word = diff > 0 ? 'Remove' : 'Add';
diff = Math.abs(diff);
if (diff) {
word = diff > 0 ? 'Remove' : 'Add';
diff = Math.abs(diff);
file.warn(
word + ' ' + diff + ' ' + plural('space', diff) +
' before this headings content',
start(children[0])
);
}
}
file.warn(
word + ' ' + diff + ' ' + plural('space', diff) +
' before this headings content',
start(children[0])
);
}
}
/*
* Closed ATX-heading always must have a space
* between their content and the final hashes,
* thus, there is no `add x spaces`.
*/
/*
* Closed ATX-heading always must have a space
* between their content and the final hashes,
* thus, there is no `add x spaces`.
*/
if (type === 'atx-closed') {
final = end(children[children.length - 1]);
diff = end(node).column - final.column - 1 - depth;
if (type === 'atx-closed') {
final = end(children[children.length - 1]);
diff = end(node).column - final.column - 1 - depth;
if (diff) {
file.warn(
'Remove ' + diff + ' ' + plural('space', diff) +
' after this headings content',
final
);
}
}
});
done();
if (diff) {
file.warn(
'Remove ' + diff + ' ' + plural('space', diff) +
' after this headings content',
final
);
}
}
});
}
/*
* Expose.
*/
module.exports = noHeadingContentIndent;

View File

@ -32,20 +32,15 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var plural = require('plur');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = noHeadingIndent;
/* Methods. */
var start = position.start;
/**
@ -54,51 +49,41 @@ var start = position.start;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noHeadingIndent(ast, file, preferred, done) {
var contents = file.toString();
var length = contents.length;
function noHeadingIndent(ast, file) {
var contents = file.toString();
var length = contents.length;
visit(ast, 'heading', function (node) {
var initial = start(node);
var begin = initial.offset;
var index = begin - 1;
var character;
var diff;
visit(ast, 'heading', function (node) {
var initial = start(node);
var begin = initial.offset;
var index = begin - 1;
var character;
var diff;
if (position.generated(node)) {
return;
if (position.generated(node)) {
return;
}
while (++index < length) {
character = contents.charAt(index);
if (character !== ' ' && character !== '\t') {
break;
}
}
diff = index - begin;
if (diff) {
file.warn(
'Remove ' + diff + ' ' + plural('space', diff) +
' before this heading',
{
line: initial.line,
column: initial.column + diff
}
while (++index < length) {
character = contents.charAt(index);
if (character !== ' ' && character !== '\t') {
break;
}
}
diff = index - begin;
if (diff) {
file.warn(
'Remove ' + diff + ' ' + plural('space', diff) +
' before this heading',
{
'line': initial.line,
'column': initial.column + diff
}
);
}
});
done();
);
}
});
}
/*
* Expose.
*/
module.exports = noHeadingIndent;

View File

@ -29,46 +29,35 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var toString = require('mdast-util-to-string');
/* Expose. */
module.exports = noHeadingPunctuation;
/**
* Warn when headings end in some characters.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {string?} [preferred='\\.,;:!?'] - Group of characters.
* @param {Function} done - Callback.
*/
function noHeadingPunctuation(ast, file, preferred, done) {
preferred = typeof preferred === 'string' ? preferred : '\\.,;:!?';
function noHeadingPunctuation(ast, file, preferred) {
preferred = typeof preferred === 'string' ? preferred : '\\.,;:!?';
visit(ast, 'heading', function (node) {
var value = toString(node);
visit(ast, 'heading', function (node) {
var value = toString(node);
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
value = value.charAt(value.length - 1);
value = value.charAt(value.length - 1);
if (new RegExp('[' + preferred + ']').test(value)) {
file.warn('Dont add a trailing `' + value + '` to headings', node);
}
});
done();
if (new RegExp('[' + preferred + ']').test(value)) {
file.warn('Dont add a trailing `' + value + '` to headings', node);
}
});
}
/*
* Expose.
*/
module.exports = noHeadingPunctuation;

View File

@ -18,15 +18,13 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = html;
/**
* Warn when HTML nodes are used.
*
@ -36,13 +34,11 @@ var position = require('unist-util-position');
* @param {Function} done - Callback.
*/
function html(ast, file, preferred, done) {
visit(ast, 'html', function (node) {
if (!position.generated(node) && !/^\s*<!--/.test(node.value)) {
file.warn('Do not use HTML in markdown', node);
}
});
visit(ast, 'html', function (node) {
if (!position.generated(node) && !/^\s*<!--/.test(node.value)) {
file.warn('Do not use HTML in markdown', node);
}
});
done();
done();
}
module.exports = html;

View File

@ -18,54 +18,42 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var toString = require('mdast-util-to-string');
/* Expose. */
module.exports = noInlinePadding;
/**
* Warn when inline nodes are padded with spaces between
* markers and content.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noInlinePadding(ast, file, preferred, done) {
visit(ast, function (node) {
var type = node.type;
var contents;
function noInlinePadding(ast, file) {
visit(ast, function (node) {
var type = node.type;
var contents;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (
type === 'emphasis' ||
type === 'strong' ||
type === 'delete' ||
type === 'image' ||
type === 'link'
) {
contents = toString(node);
if (
type === 'emphasis' ||
type === 'strong' ||
type === 'delete' ||
type === 'image' ||
type === 'link'
) {
contents = toString(node);
if (contents.charAt(0) === ' ' || contents.charAt(contents.length - 1) === ' ') {
file.warn('Dont pad `' + type + '` with inner spaces', node);
}
}
});
done();
if (contents.charAt(0) === ' ' || contents.charAt(contents.length - 1) === ' ') {
file.warn('Dont pad `' + type + '` with inner spaces', node);
}
}
});
}
/*
* Expose.
*/
module.exports = noInlinePadding;

View File

@ -15,27 +15,19 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = noLiteralURLs;
/* Methods. */
var start = position.start;
var end = position.end;
/*
* Constants.
*/
/* Constants. */
var MAILTO = 'mailto:';
/**
@ -43,35 +35,25 @@ var MAILTO = 'mailto:';
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noLiteralURLs(ast, file, preferred, done) {
visit(ast, 'link', function (node) {
var head = start(node.children[0]).column;
var tail = end(node.children[node.children.length - 1]).column;
var initial = start(node).column;
var final = end(node).column;
var value = toString(node);
function noLiteralURLs(ast, file) {
visit(ast, 'link', function (node) {
var head = start(node.children[0]).column;
var tail = end(node.children[node.children.length - 1]).column;
var initial = start(node).column;
var final = end(node).column;
var value = toString(node);
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (
initial === head &&
final === tail &&
(value === node.url || value == MAILTO + node.url)
) {
file.warn('Dont use literal URLs without angle brackets', node);
}
});
done();
if (
initial === head &&
final === tail &&
(value === node.url || value == MAILTO + node.url)
) {
file.warn('Dont use literal URLs without angle brackets', node);
}
});
}
/*
* Expose.
*/
module.exports = noLiteralURLs;

View File

@ -18,15 +18,39 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noMissingBlankLines;
/**
* Warn when there is no empty line between two block
* nodes.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
*/
function noMissingBlankLines(ast, file) {
visit(ast, function (node, index, parent) {
var next = parent && parent.children[index + 1];
if (position.generated(node)) {
return;
}
if (
next &&
isApplicable(node) &&
isApplicable(next) &&
position.start(next).line === position.end(node).line + 1
) {
file.warn('Missing blank line before block node', next);
}
});
}
/**
* Check if `node` is an applicable block-level node.
*
@ -34,51 +58,15 @@ var position = require('unist-util-position');
* @return {boolean} - Whether or not `node` is applicable.
*/
function isApplicable(node) {
return [
'paragraph',
'blockquote',
'heading',
'code',
'yaml',
'html',
'list',
'table',
'thematicBreak'
].indexOf(node.type) !== -1;
return [
'paragraph',
'blockquote',
'heading',
'code',
'yaml',
'html',
'list',
'table',
'thematicBreak'
].indexOf(node.type) !== -1;
}
/**
* Warn when there is no empty line between two block
* nodes.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noMissingBlankLines(ast, file, preferred, done) {
visit(ast, function (node, index, parent) {
var next = parent && parent.children[index + 1];
if (position.generated(node)) {
return;
}
if (
next &&
isApplicable(node) &&
isApplicable(next) &&
position.start(next).line === position.end(node).line + 1
) {
file.warn('Missing blank line before block node', next);
}
});
done();
}
/*
* Expose.
*/
module.exports = noMissingBlankLines;

View File

@ -21,15 +21,13 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noMultipleToplevelHeadings;
/**
* Warn when multiple top-level headings are used.
*
@ -38,29 +36,25 @@ var position = require('unist-util-position');
* @param {number?} [preferred=1] - Top heading level.
* @param {Function} done - Callback.
*/
function noMultipleToplevelHeadings(ast, file, preferred, done) {
var style = preferred && preferred !== true ? preferred : 1;
var topLevelheading = false;
function noMultipleToplevelHeadings(ast, file, preferred) {
var style = preferred ? preferred : 1;
var topLevelheading = false;
visit(ast, 'heading', function (node) {
var pos;
visit(ast, 'heading', function (node) {
var pos;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
if (node.depth === style) {
if (topLevelheading) {
pos = position.start(node);
if (node.depth === style) {
if (topLevelheading) {
pos = position.start(node);
file.warn('Dont use multiple top level headings (' + pos.line + ':' + pos.column + ')', node);
}
file.warn('Dont use multiple top level headings (' + pos.line + ':' + pos.column + ')', node);
}
topLevelheading = node;
}
});
done();
topLevelheading = node;
}
});
}
module.exports = noMultipleToplevelHeadings;

View File

@ -31,33 +31,28 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/**
* List of shell script file extensions (also used as code
* flags for syntax highlighting on GitHub):
*
* @see https://github.com/github/linguist/blob/5bf8cf5/lib/linguist/languages.yml#L3002
*/
/* Expose. */
module.exports = noShellDollars;
/* List of shell script file extensions (also used as code
* flags for syntax highlighting on GitHub):
* https://github.com/github/linguist/blob/5bf8cf5/lib/
* linguist/languages.yml#L3002. */
var flags = [
'sh',
'bash',
'bats',
'cgi',
'command',
'fcgi',
'ksh',
'tmux',
'tool',
'zsh'
'sh',
'bash',
'bats',
'cgi',
'command',
'fcgi',
'ksh',
'tmux',
'tool',
'zsh'
];
/**
@ -65,39 +60,26 @@ var flags = [
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noShellDollars(ast, file, preferred, done) {
visit(ast, 'code', function (node) {
var language = node.lang;
var value = node.value;
var warn;
function noShellDollars(ast, file) {
visit(ast, 'code', function (node) {
var language = node.lang;
var value = node.value;
var warn;
if (!language || position.generated(node)) {
return;
}
if (!language || position.generated(node)) {
return;
}
/*
* Check both known shell-code and unknown code.
*/
/* Check both known shell-code and unknown code. */
if (flags.indexOf(language) !== -1) {
warn = value.length && value.split('\n').every(function (line) {
return Boolean(!line.trim() || line.match(/^\s*\$\s*/));
});
if (flags.indexOf(language) !== -1) {
warn = value.length && value.split('\n').every(function (line) {
return Boolean(!line.trim() || line.match(/^\s*\$\s*/));
});
if (warn) {
file.warn('Do not use dollar signs before shell-commands', node);
}
}
});
done();
if (warn) {
file.warn('Do not use dollar signs before shell-commands', node);
}
}
});
}
/*
* Expose.
*/
module.exports = noShellDollars;

View File

@ -19,39 +19,27 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noShortcutReferenceImage;
/**
* Warn when shortcut reference images are used.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noShortcutReferenceImage(ast, file, preferred, done) {
visit(ast, 'imageReference', function (node) {
if (position.generated(node)) {
return;
}
function noShortcutReferenceImage(ast, file) {
visit(ast, 'imageReference', function (node) {
if (position.generated(node)) {
return;
}
if (node.referenceType === 'shortcut') {
file.warn('Use the trailing [] on reference images', node);
}
});
done();
if (node.referenceType === 'shortcut') {
file.warn('Use the trailing [] on reference images', node);
}
});
}
/*
* Expose.
*/
module.exports = noShortcutReferenceImage;

View File

@ -19,39 +19,27 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noShortcutReferenceLink;
/**
* Warn when shortcut reference links are used.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noShortcutReferenceLink(ast, file, preferred, done) {
visit(ast, 'linkReference', function (node) {
if (position.generated(node)) {
return;
}
function noShortcutReferenceLink(ast, file) {
visit(ast, 'linkReference', function (node) {
if (position.generated(node)) {
return;
}
if (node.referenceType === 'shortcut') {
file.warn('Use the trailing [] on reference links', node);
}
});
done();
if (node.referenceType === 'shortcut') {
file.warn('Use the trailing [] on reference links', node);
}
});
}
/*
* Expose.
*/
module.exports = noShortcutReferenceLink;

View File

@ -19,45 +19,33 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/* Expose. */
module.exports = noTableIndentation;
/**
* Warn when a table has a too much indentation.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noTableIndentation(ast, file, preferred, done) {
visit(ast, 'table', function (node) {
var contents = file.toString();
function noTableIndentation(ast, file) {
visit(ast, 'table', function (node) {
var contents = file.toString();
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
node.children.forEach(function (row) {
var fence = contents.slice(position.start(row).offset, position.start(row.children[0]).offset);
node.children.forEach(function (row) {
var fence = contents.slice(position.start(row).offset, position.start(row.children[0]).offset);
if (fence.indexOf('|') > 1) {
file.warn('Do not indent table rows', row);
}
});
if (fence.indexOf('|') > 1) {
file.warn('Do not indent table rows', row);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = noTableIndentation;

View File

@ -20,14 +20,12 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var vfileLocation = require('vfile-location');
/* Expose. */
module.exports = noTabs;
/**
* Warn when hard-tabs instead of spaces are used.
*
@ -37,22 +35,16 @@ var vfileLocation = require('vfile-location');
* @param {Function} done - Callback.
*/
function noTabs(ast, file, preferred, done) {
var content = file.toString();
var location = vfileLocation(file);
var index = -1;
var length = content.length;
var content = file.toString();
var location = vfileLocation(file);
var index = -1;
var length = content.length;
while (++index < length) {
if (content.charAt(index) === '\t') {
file.warn('Use spaces instead of hard-tabs', location.toPosition(index));
}
while (++index < length) {
if (content.charAt(index) === '\t') {
file.warn('Use spaces instead of hard-tabs', location.toPosition(index));
}
}
done();
done();
}
/*
* Expose.
*/
module.exports = noTabs;

View File

@ -17,66 +17,56 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var position = require('unist-util-position');
var visit = require('unist-util-visit');
/* Expose. */
module.exports = noUnusedDefinitions;
/**
* Warn when references to undefined definitions are found.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noUnusedDefinitions(ast, file, preferred, done) {
var map = {};
function noUnusedDefinitions(ast, file) {
var map = {};
/**
* Check `node`.
*
* @param {Node} node - Node.
*/
function mark(node) {
if (position.generated(node)) {
return;
}
visit(ast, 'definition', mark);
visit(ast, 'footnoteDefinition', mark);
map[node.identifier.toUpperCase()] = true;
visit(ast, 'imageReference', find);
visit(ast, 'linkReference', find);
visit(ast, 'footnoteReference', find);
return;
/**
* Check `node`.
*
* @param {Node} node - Node.
*/
function mark(node) {
if (position.generated(node)) {
return;
}
/**
* Mark `node`.
*
* @param {Node} node - Node.
*/
function find(node) {
if (position.generated(node)) {
return;
}
map[node.identifier.toUpperCase()] = true;
}
if (!map[node.identifier.toUpperCase()]) {
file.warn('Found reference to undefined definition', node);
}
/**
* Mark `node`.
*
* @param {Node} node - Node.
*/
function find(node) {
if (position.generated(node)) {
return;
}
visit(ast, 'definition', mark);
visit(ast, 'footnoteDefinition', mark);
visit(ast, 'imageReference', find);
visit(ast, 'linkReference', find);
visit(ast, 'footnoteReference', find);
done();
if (!map[node.identifier.toUpperCase()]) {
file.warn('Found reference to undefined definition', node);
}
}
}
/*
* Expose.
*/
module.exports = noUnusedDefinitions;

View File

@ -17,76 +17,66 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var position = require('unist-util-position');
var visit = require('unist-util-visit');
/* Expose. */
module.exports = noUnusedDefinitions;
/**
* Warn when unused definitions are found.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noUnusedDefinitions(ast, file, preferred, done) {
var map = {};
var identifier;
function noUnusedDefinitions(ast, file) {
var map = {};
var identifier;
/**
* Check `node`.
*
* @param {Node} node - Node.
*/
function find(node) {
if (position.generated(node)) {
return;
}
visit(ast, 'definition', find);
visit(ast, 'footnoteDefinition', find);
map[node.identifier.toUpperCase()] = {
'node': node,
'used': false
};
visit(ast, 'imageReference', mark);
visit(ast, 'linkReference', mark);
visit(ast, 'footnoteReference', mark);
for (identifier in map) {
if (!map[identifier].used) {
file.warn('Found unused definition', map[identifier].node);
}
}
return;
/**
* Check `node`.
*
* @param {Node} node - Node.
*/
function find(node) {
if (position.generated(node)) {
return;
}
/**
* Mark `node`.
*
* @param {Node} node - Node.
*/
function mark(node) {
var info = map[node.identifier.toUpperCase()];
map[node.identifier.toUpperCase()] = {
node: node,
used: false
};
}
if (position.generated(node) || !info) {
return;
}
/**
* Mark `node`.
*
* @param {Node} node - Node.
*/
function mark(node) {
var info = map[node.identifier.toUpperCase()];
info.used = true;
if (position.generated(node) || !info) {
return;
}
visit(ast, 'definition', find);
visit(ast, 'footnoteDefinition', find);
visit(ast, 'imageReference', mark);
visit(ast, 'linkReference', mark);
visit(ast, 'footnoteReference', mark);
for (identifier in map) {
if (!map[identifier].used) {
file.warn('Found unused definition', map[identifier].node);
}
}
done();
info.used = true;
}
}
/*
* Expose.
*/
module.exports = noUnusedDefinitions;

View File

@ -29,29 +29,21 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = orderedListMarkerStyle;
/* Methods. */
var start = position.start;
/*
* Valid styles.
*/
/* Valid styles. */
var STYLES = {
')': true,
'.': true,
'null': true
')': true,
'.': true,
'null': true
};
/**
@ -63,58 +55,44 @@ var STYLES = {
* @param {string?} [preferred='consistent'] - Ordered list
* marker style, either `'.'` or `')'`, defaulting to the
* first found style.
* @param {Function} done - Callback.
*/
function orderedListMarkerStyle(ast, file, preferred, done) {
var contents = file.toString();
function orderedListMarkerStyle(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (STYLES[preferred] !== true) {
file.fail('Invalid ordered list-item marker style `' + preferred + '`: use either `\'.\'` or `\')\'`');
done();
if (STYLES[preferred] !== true) {
file.fail('Invalid ordered list-item marker style `' + preferred + '`: use either `\'.\'` or `\')\'`');
return;
}
return;
visit(ast, 'list', function (node) {
var items = node.children;
if (!node.ordered) {
return;
}
visit(ast, 'list', function (node) {
var items = node.children;
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
if (!node.ordered) {
return;
}
if (position.generated(item)) {
return;
}
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
marker = contents.slice(initial, final).replace(/\s|\d/g, '');
if (position.generated(item)) {
return;
}
/* Support checkboxes. */
marker = marker.replace(/\[[x ]?\]\s*$/i, '');
marker = contents.slice(initial, final).replace(/\s|\d/g, '');
/*
* Support checkboxes.
*/
marker = marker.replace(/\[[x ]?\]\s*$/i, '');
if (!preferred) {
preferred = marker;
} else if (marker !== preferred) {
file.warn('Marker style should be `' + preferred + '`', item);
}
});
if (!preferred) {
preferred = marker;
} else if (marker !== preferred) {
file.warn('Marker style should be `' + preferred + '`', item);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = orderedListMarkerStyle;

View File

@ -45,29 +45,21 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = orderedListMarkerValue;
/* Methods. */
var start = position.start;
/*
* Valid styles.
*/
/* Valid styles. */
var STYLES = {
'ordered': true,
'single': true,
'one': true
ordered: true,
single: true,
one: true
};
/**
@ -79,82 +71,56 @@ var STYLES = {
* @param {string?} [preferred='ordered'] - Ordered list
* marker value, either `'one'` or `'ordered'`,
* defaulting to the latter.
* @param {Function} done - Callback.
*/
function orderedListMarkerValue(ast, file, preferred, done) {
var contents = file.toString();
function orderedListMarkerValue(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' ? 'ordered' : preferred;
preferred = typeof preferred === 'string' ? preferred : 'ordered';
if (STYLES[preferred] !== true) {
file.fail('Invalid ordered list-item marker value `' + preferred + '`: use either `\'ordered\'` or `\'one\'`');
done();
if (STYLES[preferred] !== true) {
file.fail('Invalid ordered list-item marker value `' + preferred + '`: use either `\'ordered\'` or `\'one\'`');
return;
}
return;
visit(ast, 'list', function (node) {
var items = node.children;
var shouldBe = (preferred === 'one' ? 1 : node.start) || 1;
/* Ignore unordered lists. */
if (!node.ordered) {
return;
}
visit(ast, 'list', function (node) {
var items = node.children;
var shouldBe = (preferred === 'one' ? 1 : node.start) || 1;
items.forEach(function (item, index) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
/*
* Ignore unordered lists.
*/
/* Ignore first list item. */
if (index === 0) {
return;
}
if (!node.ordered) {
return;
}
/* Increase the expected line number when in
* `ordered` mode. */
if (preferred === 'ordered') {
shouldBe++;
}
items.forEach(function (item, index) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
/* Ignore generated nodes. */
if (position.generated(item)) {
return;
}
/*
* Ignore first list item.
*/
marker = contents.slice(initial, final).replace(/[\s\.\)]/g, '');
if (index === 0) {
return;
}
/* Support checkboxes. */
marker = Number(marker.replace(/\[[x ]?\]\s*$/i, ''));
/*
* Increase the expected line number when in
* `ordered` mode.
*/
if (preferred === 'ordered') {
shouldBe++;
}
/*
* Ignore generated nodes.
*/
if (position.generated(item)) {
return;
}
marker = contents.slice(initial, final).replace(/[\s\.\)]/g, '');
/*
* Support checkboxes.
*/
marker = Number(marker.replace(/\[[x ]?\]\s*$/i, ''));
if (marker !== shouldBe) {
file.warn('Marker should be `' + shouldBe + '`, was `' + marker + '`', item);
}
});
if (marker !== shouldBe) {
file.warn('Marker should be `' + shouldBe + '`, was `' + marker + '`', item);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = orderedListMarkerValue;

View File

@ -24,33 +24,17 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = ruleStyle;
/* Methods. */
var start = position.start;
var end = position.end;
/**
* Warn when a given style is invalid.
*
* @param {*} style - `*`, `_`, ` ` (space), or `-`.
* @return {boolean} - Whether or not `style` is a
* valid rule style.
*/
function validateRuleStyle(style) {
return style === null || !/[^-_* ]/.test(style);
}
/**
* Warn when the horizontal rules violate a given or
* detected style.
@ -59,45 +43,45 @@ function validateRuleStyle(style) {
* @param {File} file - Virtual file.
* @param {string?} [preferred='consistent'] - A valid
* horizontal rule, defaulting to the first found style.
* @param {Function} done - Callback.
*/
function ruleStyle(ast, file, preferred, done) {
var contents = file.toString();
function ruleStyle(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (validateRuleStyle(preferred) !== true) {
file.fail('Invalid preferred rule-style: provide a valid markdown rule, or `\'consistent\'`');
done();
if (validateRuleStyle(preferred) !== true) {
file.fail('Invalid preferred rule-style: provide a valid markdown rule, or `\'consistent\'`');
return;
}
return;
visit(ast, 'thematicBreak', function (node) {
var initial = start(node).offset;
var final = end(node).offset;
var hr;
if (position.generated(node)) {
return;
}
visit(ast, 'thematicBreak', function (node) {
var initial = start(node).offset;
var final = end(node).offset;
var hr;
hr = contents.slice(initial, final);
if (position.generated(node)) {
return;
}
hr = contents.slice(initial, final);
if (preferred) {
if (hr !== preferred) {
file.warn('Rules should use `' + preferred + '`', node);
}
} else {
preferred = hr;
}
});
done();
if (preferred) {
if (hr !== preferred) {
file.warn('Rules should use `' + preferred + '`', node);
}
} else {
preferred = hr;
}
});
}
/*
* Expose.
/**
* Warn when a given style is invalid.
*
* @param {*} style - `*`, `_`, ` ` (space), or `-`.
* @return {boolean} - Whether or not `style` is a
* valid rule style.
*/
module.exports = ruleStyle;
function validateRuleStyle(style) {
return style === null || !/[^-_* ]/.test(style);
}

View File

@ -24,23 +24,18 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Map of valid markers.
*/
/* Expose. */
module.exports = strongMarker;
/* Map of valid markers. */
var MARKERS = {
'*': true,
'_': true,
'null': true
'*': true,
'_': true,
'null': true
};
/**
@ -50,36 +45,28 @@ var MARKERS = {
* @param {File} file - Virtual file.
* @param {string?} [preferred='consistent'] - Preferred
* marker, either `"*"` or `"_"`, or `"consistent"`.
* @param {Function} done - Callback.
*/
function strongMarker(ast, file, preferred, done) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
function strongMarker(ast, file, preferred) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (MARKERS[preferred] !== true) {
file.fail('Invalid strong marker `' + preferred + '`: use either `\'consistent\'`, `\'*\'`, or `\'_\'`');
} else {
visit(ast, 'strong', function (node) {
var marker = file.toString().charAt(position.start(node).offset);
if (MARKERS[preferred] !== true) {
file.fail('Invalid strong marker `' + preferred + '`: use either `\'consistent\'`, `\'*\'`, or `\'_\'`');
return;
}
if (position.generated(node)) {
return;
}
visit(ast, 'strong', function (node) {
var marker = file.toString().charAt(position.start(node).offset);
if (preferred) {
if (marker !== preferred) {
file.warn('Strong should use `' + preferred + '` as a marker', node);
}
} else {
preferred = marker;
}
});
if (position.generated(node)) {
return;
}
done();
if (preferred) {
if (marker !== preferred) {
file.warn('Strong should use `' + preferred + '` as a marker', node);
}
} else {
preferred = marker;
}
});
}
/*
* Expose.
*/
module.exports = strongMarker;

View File

@ -31,30 +31,24 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* eslint-disable max-params */
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = tableCellPadding;
/* Methods. */
var start = position.start;
var end = position.end;
/*
* Valid styles.
*/
/* Valid styles. */
var STYLES = {
'null': true,
'padded': true,
'compact': true
null: true,
padded: true,
compact: true
};
/**
@ -66,121 +60,113 @@ var STYLES = {
* at least a space), `compact` (for no spaces when
* possible), or `consistent`, which defaults to the
* first found style.
* @param {Function} done - Callback.
*/
function tableCellPadding(ast, file, preferred, done) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
function tableCellPadding(ast, file, preferred) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (STYLES[preferred] !== true) {
file.fail('Invalid table-cell-padding style `' + preferred + '`');
if (STYLES[preferred] !== true) {
file.fail('Invalid table-cell-padding style `' + preferred + '`');
return;
}
visit(ast, 'table', function (node) {
var children = node.children;
var contents = file.toString();
var starts = [];
var ends = [];
var locations;
var positions;
var style;
var type;
var warning;
if (position.generated(node)) {
return;
}
visit(ast, 'table', function (node) {
var children = node.children;
var contents = file.toString();
var starts = [];
var ends = [];
var locations;
var positions;
var style;
var type;
var warning;
/**
* Check a fence. Checks both its initial spacing
* (between a cell and the fence), and its final
* spacing (between the fence and the next cell).
*
* @param {number} initial - Starting index.
* @param {number} final - Closing index.
* @param {Node} cell - Table cell.
* @param {Node?} next - Following cell.
* @param {number} index - Position of `cell` in
* its parent.
*/
function check(initial, final, cell, next, index) {
var fence = contents.slice(initial, final);
var pos = fence.indexOf('|');
if (position.generated(node)) {
return;
if (
cell &&
pos !== -1 &&
(ends[index] === undefined || pos < ends[index])
) {
ends[index] = pos;
}
if (next && pos !== -1) {
pos = fence.length - pos - 1;
if (starts[index + 1] === undefined || pos < starts[index + 1]) {
starts[index + 1] = pos;
}
}
}
/**
* Check a fence. Checks both its initial spacing
* (between a cell and the fence), and its final
* spacing (between the fence and the next cell).
*
* @param {number} initial - Starting index.
* @param {number} final - Closing index.
* @param {Node} cell - Table cell.
* @param {Node?} next - Following cell.
* @param {number} index - Position of `cell` in
* its parent.
*/
function check(initial, final, cell, next, index) {
var fence = contents.slice(initial, final);
var pos = fence.indexOf('|');
children.forEach(function (row) {
var cells = row.children;
if (
cell &&
pos !== -1 &&
(
ends[index] === undefined ||
pos < ends[index]
)
) {
ends[index] = pos;
}
check(start(row).offset, start(cells[0]).offset, null, cells[0], -1);
if (next && pos !== -1) {
pos = fence.length - pos - 1;
cells.forEach(function (cell, index) {
var next = cells[index + 1] || null;
var final = start(next).offset || end(row).offset;
if (starts[index + 1] === undefined || pos < starts[index + 1]) {
starts[index + 1] = pos;
}
}
}
children.forEach(function (row) {
var cells = row.children;
check(start(row).offset, start(cells[0]).offset, null, cells[0], -1);
cells.forEach(function (cell, index) {
var next = cells[index + 1] || null;
var final = start(next).offset || end(row).offset;
check(end(cell).offset, final, cell, next, index);
});
});
positions = starts.concat(ends);
style = preferred === 'padded' ? 1 : preferred === 'compact' ? 0 : null;
if (preferred === 'padded') {
style = 1;
} else if (preferred === 'compact') {
style = 0;
} else {
positions.some(function (pos) {
/*
* `some` skips non-existant indices, so
* there's no need to check for `!isNaN`.
*/
style = Math.min(pos, 1);
return true;
});
}
locations = children[0].children.map(function (cell) {
return start(cell);
}).concat(children[0].children.map(function (cell) {
return end(cell);
}));
type = style === 1 ? 'padded' : 'compact';
warning = 'Cell should be ' + type + ', isnt';
positions.forEach(function (diff, index) {
if (diff !== style && diff !== undefined && diff !== null) {
file.warn(warning, locations[index]);
}
});
check(end(cell).offset, final, cell, next, index);
});
});
done();
positions = starts.concat(ends);
if (preferred === 'padded') {
style = 1;
} else if (preferred === 'compact') {
style = 0;
} else {
style = null;
}
if (preferred === 'padded') {
style = 1;
} else if (preferred === 'compact') {
style = 0;
} else {
positions.some(function (pos) {
/* `some` skips non-existant indices, so
* there's no need to check for `!isNaN`. */
style = Math.min(pos, 1);
return true;
});
}
locations = children[0].children.map(function (cell) {
return start(cell);
}).concat(children[0].children.map(function (cell) {
return end(cell);
}));
type = style === 1 ? 'padded' : 'compact';
warning = 'Cell should be ' + type + ', isnt';
positions.forEach(function (diff, index) {
if (diff !== style && diff !== undefined && diff !== null) {
file.warn(warning, locations[index]);
}
});
});
}
/*
* Expose.
*/
module.exports = tableCellPadding;

View File

@ -19,19 +19,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = tablePipeAlignment;
/* Methods. */
var start = position.start;
var end = position.end;
@ -40,69 +35,59 @@ var end = position.end;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function tablePipeAlignment(ast, file, preferred, done) {
visit(ast, 'table', function (node) {
var contents = file.toString();
var indices = [];
var offset;
var line;
function tablePipeAlignment(ast, file) {
visit(ast, 'table', function (node) {
var contents = file.toString();
var indices = [];
var offset;
var line;
if (position.generated(node)) {
return;
}
if (position.generated(node)) {
return;
}
/**
* Check all pipes after each column are at
* aligned.
*
* @param {number} initial - Starting index.
* @param {number} final - Closing index.
* @param {number} index - Position of cell in
* its parent.
*/
function check(initial, final, index) {
var pos = initial + contents.slice(initial, final).indexOf('|') - offset + 1;
/**
* Check all pipes after each column are at
* aligned.
*
* @param {number} initial - Starting index.
* @param {number} final - Closing index.
* @param {number} index - Position of cell in
* its parent.
*/
function check(initial, final, index) {
var pos = initial + contents.slice(initial, final).indexOf('|') - offset + 1;
if (indices[index] === undefined || indices[index] === null) {
indices[index] = pos;
} else if (pos !== indices[index]) {
file.warn('Misaligned table fence', {
'start': {
'line': line,
'column': pos
},
'end': {
'line': line,
'column': pos + 1
}
});
}
}
node.children.forEach(function (row) {
var cells = row.children;
line = start(row).line;
offset = start(row).offset;
check(start(row).offset, start(cells[0]).offset, 0);
row.children.forEach(function (cell, index) {
var next = start(cells[index + 1]).offset || end(row).offset;
check(end(cell).offset, next, index + 1);
});
if (indices[index] === undefined || indices[index] === null) {
indices[index] = pos;
} else if (pos !== indices[index]) {
file.warn('Misaligned table fence', {
start: {
line: line,
column: pos
},
end: {
line: line,
column: pos + 1
}
});
}
}
node.children.forEach(function (row) {
var cells = row.children;
line = start(row).line;
offset = start(row).offset;
check(start(row).offset, start(cells[0]).offset, 0);
row.children.forEach(function (cell, index) {
var next = start(cells[index + 1]).offset || end(row).offset;
check(end(cell).offset, next, index + 1);
});
});
done();
});
}
/*
* Expose.
*/
module.exports = tablePipeAlignment;

View File

@ -19,19 +19,14 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = tablePipes;
/* Methods. */
var start = position.start;
var end = position.end;
@ -40,39 +35,29 @@ var end = position.end;
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function tablePipes(ast, file, preferred, done) {
visit(ast, 'table', function (node) {
var contents = file.toString();
function tablePipes(ast, file) {
visit(ast, 'table', function (node) {
var contents = file.toString();
node.children.forEach(function (row) {
var cells = row.children;
var head = cells[0];
var tail = cells[cells.length - 1];
var initial = contents.slice(start(row).offset, start(head).offset);
var final = contents.slice(end(tail).offset, end(row).offset);
node.children.forEach(function (row) {
var cells = row.children;
var head = cells[0];
var tail = cells[cells.length - 1];
var initial = contents.slice(start(row).offset, start(head).offset);
var final = contents.slice(end(tail).offset, end(row).offset);
if (position.generated(row)) {
return;
}
if (position.generated(row)) {
return;
}
if (initial.indexOf('|') === -1) {
file.warn('Missing initial pipe in table fence', start(row));
}
if (initial.indexOf('|') === -1) {
file.warn('Missing initial pipe in table fence', start(row));
}
if (final.indexOf('|') === -1) {
file.warn('Missing final pipe in table fence', end(row));
}
});
if (final.indexOf('|') === -1) {
file.warn('Missing final pipe in table fence', end(row));
}
});
done();
});
}
/*
* Expose.
*/
module.exports = tablePipes;

View File

@ -33,30 +33,22 @@
'use strict';
/* eslint-env commonjs */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
var position = require('unist-util-position');
/*
* Methods.
*/
/* Expose. */
module.exports = unorderedListMarkerStyle;
/* Methods. */
var start = position.start;
/*
* Valid styles.
*/
/* Valid styles. */
var STYLES = {
'-': true,
'*': true,
'+': true,
'null': true
'-': true,
'*': true,
'+': true,
'null': true
};
/**
@ -68,58 +60,44 @@ var STYLES = {
* @param {string?} [preferred='consistent'] - Unordered
* list marker style, either `'-'`, `'*'`, or `'+'`,
* defaulting to the first found style.
* @param {Function} done - Callback.
*/
function unorderedListMarkerStyle(ast, file, preferred, done) {
var contents = file.toString();
function unorderedListMarkerStyle(ast, file, preferred) {
var contents = file.toString();
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
if (STYLES[preferred] !== true) {
file.fail('Invalid unordered list-item marker style `' + preferred + '`: use either `\'-\'`, `\'*\'`, or `\'+\'`');
done();
if (STYLES[preferred] !== true) {
file.fail('Invalid unordered list-item marker style `' + preferred + '`: use either `\'-\'`, `\'*\'`, or `\'+\'`');
return;
}
return;
visit(ast, 'list', function (node) {
var items = node.children;
if (node.ordered) {
return;
}
visit(ast, 'list', function (node) {
var items = node.children;
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
if (node.ordered) {
return;
}
if (position.generated(item)) {
return;
}
items.forEach(function (item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
marker = contents.slice(initial, final).replace(/\s/g, '');
if (position.generated(item)) {
return;
}
/* Support checkboxes. */
marker = marker.replace(/\[[x ]?\]\s*$/i, '');
marker = contents.slice(initial, final).replace(/\s/g, '');
/*
* Support checkboxes.
*/
marker = marker.replace(/\[[x ]?\]\s*$/i, '');
if (!preferred) {
preferred = marker;
} else if (marker !== preferred) {
file.warn('Marker style should be `' + preferred + '`', item);
}
});
if (!preferred) {
preferred = marker;
} else if (marker !== preferred) {
file.warn('Marker style should be `' + preferred + '`', item);
}
});
done();
});
}
/*
* Expose.
*/
module.exports = unorderedListMarkerStyle;

View File

@ -40,11 +40,9 @@
"devDependencies": {
"browserify": "^13.0.0",
"dox": "^0.8.0",
"eslint": "^2.0.0",
"esmangle": "^1.0.0",
"istanbul": "^0.4.0",
"jscs": "^3.0.0",
"jscs-jsdoc": "^2.0.0",
"mocha": "^2.0.0",
"remark": "^5.0.0",
"remark-cli": "^1.0.0",
"remark-comment-config": "^4.0.0",
@ -60,11 +58,44 @@
"build-bundle": "browserify index.js --bare -s remarkLint > remark-lint.js",
"build-mangle": "esmangle remark-lint.js > remark-lint.min.js",
"build": "npm run build-rules && npm run build-md && npm run build-bundle && npm run build-mangle",
"lint-api": "eslint .",
"lint-style": "jscs . --reporter inline",
"lint": "npm run lint-api && npm run lint-style",
"lint": "xo",
"test-api": "mocha --check-leaks test/index.js",
"test-coverage": "istanbul cover _mocha -- test/index.js",
"test": "npm run build && npm run lint && npm run test-coverage"
},
"nyc": {
"check-coverage": true,
"lines": 100,
"functions": 100,
"branches": 100
},
"xo": {
"space": true,
"rules": {
"guard-for-in": "off",
"no-eq-null": "off",
"eqeqeq": "off"
},
"ignores": [
"remark-lint.js",
"remark-lint.min.js"
]
},
"remarkConfig": {
"output": true,
"plugins": {
"comment-config": null,
"github": null,
"toc": {
"tight": true
},
"./": {
"no-missing-blank-lines": false
},
"validate-links": null
},
"settings": {
"bullet": "*"
}
}
}

View File

@ -10,12 +10,7 @@
'use strict';
/* eslint-env node */
/*
* Dependencies.
*/
/* Dependencies. */
var fs = require('fs');
var path = require('path');
var dox = require('dox');
@ -24,12 +19,144 @@ var toc = require('remark-toc');
var rules = require('../lib/rules');
var additional = require('./additional.json');
/*
* Methods.
*/
/* Methods. */
var exists = fs.existsSync;
var children = [];
/* Add main heading. */
children.push({
type: 'heading',
depth: 1,
children: [{
type: 'text',
value: 'List of Rules'
}]
});
/* Add main description. */
children.push(
{
type: 'paragraph',
children: [{
type: 'text',
value: 'This document describes all available rules, what they\n' +
'check for, examples of what they warn for, and how to\n' +
'fix their warnings.'
}]
},
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'See the readme for a '
},
{
type: 'link',
url: 'https://github.com/wooorm/remark-lint#list-of-external-rules',
children: [{
type: 'text',
value: 'list of external rules'
}]
},
{
type: 'text',
value: '.'
}
]
}
);
/* Add the rules heading. */
children.push({
type: 'heading',
depth: 2,
children: [{
type: 'text',
value: 'Rules'
}]
});
/* Add a section on how to turn of rules. */
children.push({
type: 'paragraph',
children: [{
type: 'text',
value: 'Remember that rules can always be turned off by\n' +
'passing false. In addition, when reset is given, values can\n' +
'be null or undefined in order to be ignored.'
}]
});
/* Add the table-of-contents heading. */
children.push({
type: 'heading',
depth: 3,
children: [{
type: 'text',
value: 'Table of Contents'
}]
});
/* Add rules. */
Object.keys(additional).sort()
.concat(Object.keys(rules).sort())
.forEach(function (ruleId) {
var description;
var filePath;
var example;
var code;
var tags;
filePath = path.join('lib', 'rules', ruleId + '.js');
if (exists(filePath)) {
code = fs.readFileSync(filePath, 'utf-8');
tags = dox.parseComments(code)[0].tags;
description = find(tags, 'fileoverview');
example = find(tags, 'example');
if (!description) {
throw new Error(ruleId + ' is missing a `@fileoverview`');
}
description = description.string;
example = example && example.string;
} else {
description = additional[ruleId].description;
example = additional[ruleId].example;
}
children.push({
type: 'heading',
depth: 3,
children: [{
type: 'text',
value: ruleId
}]
});
if (example) {
children.push({
type: 'code',
lang: 'md',
value: example
});
}
children = children.concat(remark().parse(description).children);
});
/* Node. */
var node = {type: 'root', children: children};
/* Add toc. */
remark().use(toc).run(node);
/* Write. */
fs.writeFileSync('doc/rules.md', remark().stringify(node));
/**
* Find the first tag in `tags` with a type set to `key`.
*
@ -38,180 +165,17 @@ var exists = fs.existsSync;
* @return {Object?} - Tag, when found.
*/
function find(tags, key) {
var value = null;
var value = null;
tags.some(function (tag) {
if (tag && tag.type === key) {
value = tag;
tags.some(function (tag) {
if (tag && tag.type === key) {
value = tag;
return true;
}
});
return true;
}
return value;
return false;
});
return value;
}
var children = [];
/*
* Add main heading.
*/
children.push({
'type': 'heading',
'depth': 1,
'children': [{
'type': 'text',
'value': 'List of Rules'
}]
});
/*
* Add main description.
*/
children.push(
{
'type': 'paragraph',
'children': [{
'type': 'text',
'value': 'This document describes all available rules, what they\n' +
'check for, examples of what they warn for, and how to\n' +
'fix their warnings.'
}]
},
{
'type': 'paragraph',
'children': [
{
'type': 'text',
'value': 'See the readme for a '
},
{
'type': 'link',
'url': 'https://github.com/wooorm/remark-lint#list-of-external-rules',
'children': [{
'type': 'text',
'value': 'list of external rules'
}]
},
{
'type': 'text',
'value': '.'
}
]
}
);
/*
* Add the rules heading.
*/
children.push({
'type': 'heading',
'depth': 2,
'children': [{
'type': 'text',
'value': 'Rules'
}]
});
/*
* Add a section on how to turn of rules.
*/
children.push({
'type': 'paragraph',
'children': [{
'type': 'text',
'value': 'Remember that rules can always be turned off by\n' +
'passing false. In addition, when reset is given, values can\n' +
'be null or undefined in order to be ignored.'
}]
});
/*
* Add the table-of-contents heading.
*/
children.push({
'type': 'heading',
'depth': 3,
'children': [{
'type': 'text',
'value': 'Table of Contents'
}]
});
/*
* Add rules.
*/
Object.keys(additional).sort()
.concat(Object.keys(rules).sort())
.forEach(function (ruleId) {
var description;
var filePath;
var example;
var code;
var tags;
filePath = path.join('lib', 'rules', ruleId + '.js');
if (exists(filePath)) {
code = fs.readFileSync(filePath, 'utf-8');
tags = dox.parseComments(code)[0].tags;
description = find(tags, 'fileoverview');
example = find(tags, 'example');
if (!description) {
throw new Error(ruleId + ' is missing a `@fileoverview`');
}
description = description.string;
example = example && example.string;
} else {
description = additional[ruleId].description;
example = additional[ruleId].example;
}
children.push({
'type': 'heading',
'depth': 3,
'children': [{
'type': 'text',
'value': ruleId
}]
});
if (example) {
children.push({
'type': 'code',
'lang': 'md',
'value': example
});
}
children = children.concat(remark().parse(description).children);
});
/*
* Node.
*/
var node = {
'type': 'root',
'children': children
};
/*
* Add toc.
*/
remark().use(toc).run(node);
/*
* Write.
*/
fs.writeFileSync('doc/rules.md', remark().stringify(node));

View File

@ -10,12 +10,7 @@
'use strict';
/* eslint-env node */
/*
* Dependencies.
*/
/* Dependencies. */
var visit = require('unist-util-visit');
/**
@ -24,9 +19,9 @@ var visit = require('unist-util-visit');
* @param {Node} ast - Root node.
*/
function transformer(ast) {
visit(ast, function (node) {
node.position = undefined;
});
visit(ast, function (node) {
node.position = undefined;
});
}
/**
@ -35,7 +30,7 @@ function transformer(ast) {
* @return {Function} - See `transformer`.
*/
function attacher() {
return transformer;
return transformer;
}
/*