Refactor code-style

This commit is contained in:
Titus Wormer 2018-05-18 12:30:21 +02:00
parent b4f1baec9e
commit 6c1b4cf6b0
85 changed files with 2770 additions and 2422 deletions

3
.prettierignore Normal file
View File

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

View File

@ -1,4 +1,4 @@
exports.settings = {bullet: '*', paddedTable: false};
exports.settings = {bullet: '*', paddedTable: false}
exports.plugins = [
require('./packages/remark-preset-lint-recommended'),
@ -9,4 +9,4 @@ exports.plugins = [
require('remark-validate-links'),
require('./script/plugin/list-of-presets'),
require('./script/plugin/list-of-rules')
];
]

View File

@ -38,6 +38,7 @@
"mdast-zone": "^3.0.0",
"nyc": "^11.0.0",
"parse-author": "^2.0.0",
"prettier": "^1.12.1",
"remark": "^9.0.0",
"remark-cli": "^5.0.0",
"remark-comment-config": "^5.0.0",
@ -55,16 +56,16 @@
},
"scripts": {
"prepublish": "lerna bootstrap",
"build-presets": "node script/build-presets",
"build-rules": "node script/build-rules",
"build-md": "remark . -qfo",
"build-bundle": "browserify packages/remark-lint/index.js --bare -s remarkLint > remark-lint.js",
"build-mangle": "esmangle remark-lint.js > remark-lint.min.js",
"build": "npm run build-presets && npm run build-rules && npm run build-md && npm run build-bundle && npm run build-mangle",
"lint": "xo",
"format": "remark . -qfo && prettier --write '**/*.js' && xo --fix",
"generate:presets": "node script/build-presets",
"generate:rules": "node script/build-rules",
"generate": "npm run generate:presets && npm run generate:rules",
"build:bundle": "browserify packages/remark-lint/index.js --bare -s remarkLint > remark-lint.js",
"build:mangle": "esmangle remark-lint.js > remark-lint.min.js",
"build": "npm run build:bundle && npm run build:mangle",
"test-api": "node test",
"test-coverage": "nyc --reporter lcov tape test.js",
"test": "npm run build && npm run lint && npm run test-coverage"
"test": "npm run generate && npm run format && npm run build && npm run test-coverage"
},
"nyc": {
"check-coverage": true,
@ -72,8 +73,16 @@
"functions": 100,
"branches": 100
},
"prettier": {
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"bracketSpacing": false,
"semi": false,
"trailingComma": "none"
},
"xo": {
"space": true,
"prettier": true,
"esnext": false,
"rules": {
"guard-for-in": "off",

View File

@ -45,59 +45,64 @@
* 9:3: Add 1 space between blockquote and content
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var plural = require('plur');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var plural = require('plur')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:blockquote-indentation', blockquoteIndentation);
module.exports = rule(
'remark-lint:blockquote-indentation',
blockquoteIndentation
)
function blockquoteIndentation(tree, file, preferred) {
preferred = isNaN(preferred) || typeof preferred !== 'number' ? null : preferred;
function blockquoteIndentation(tree, file, pref) {
pref = typeof pref === 'number' && !isNaN(pref) ? pref : null
visit(tree, 'blockquote', visitor);
visit(tree, 'blockquote', visitor)
function visitor(node) {
var indent;
var diff;
var word;
var diff
var diffAbs
var reason
if (generated(node) || node.children.length === 0) {
return;
return
}
if (preferred) {
indent = check(node);
diff = preferred - indent;
word = diff > 0 ? 'Add' : 'Remove';
diff = Math.abs(diff);
if (pref) {
diff = pref - check(node)
if (diff !== 0) {
file.message(
word + ' ' + diff + ' ' + plural('space', diff) +
' between blockquote and content',
position.start(node.children[0])
);
diffAbs = Math.abs(diff)
reason =
(diff > 0 ? 'Add' : 'Remove') +
' ' +
diffAbs +
' ' +
plural('space', diffAbs) +
' between blockquote and content'
file.message(reason, position.start(node.children[0]))
}
} else {
preferred = check(node);
pref = check(node)
}
}
}
function check(node) {
var head = node.children[0];
var indentation = position.start(head).column - position.start(node).column;
var padding = toString(head).match(/^ +/);
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;
indentation += padding[0].length
}
return indentation;
return indentation
}

View File

@ -68,103 +68,94 @@
* 1:1: Invalid checked checkbox marker `!`: use either `'x'`, or `'X'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var vfileLocation = require('vfile-location')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:checkbox-character-style', checkboxCharacterStyle);
module.exports = rule(
'remark-lint:checkbox-character-style',
checkboxCharacterStyle
)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
var CHECKED = {x: true, X: true};
var UNCHECKED = {' ': true, '\t': true};
var checked = {x: true, X: true}
var unchecked = {' ': true, '\t': true}
var types = {true: 'checked', false: 'unchecked'}
function checkboxCharacterStyle(tree, file, preferred) {
var contents = file.toString();
var location = vfileLocation(file);
function checkboxCharacterStyle(tree, file, pref) {
var contents = String(file)
var location = vfileLocation(file)
if (preferred === 'consistent' || typeof preferred !== 'object') {
preferred = {};
}
pref = typeof pref === 'object' ? pref : {}
if (!preferred.unchecked) {
preferred.unchecked = null;
}
if (!preferred.checked) {
preferred.checked = null;
}
if (
preferred.unchecked !== null &&
UNCHECKED[preferred.unchecked] !== true
) {
if (pref.unchecked && unchecked[pref.unchecked] !== true) {
file.fail(
'Invalid unchecked checkbox marker `' +
preferred.unchecked +
'`: use either `\'\\t\'`, or `\' \'`'
);
pref.unchecked +
"`: use either `'\\t'`, or `' '`"
)
}
if (
preferred.checked !== null &&
CHECKED[preferred.checked] !== true
) {
if (pref.checked && checked[pref.checked] !== true) {
file.fail(
'Invalid checked checkbox marker `' +
preferred.checked +
'`: use either `\'x\'`, or `\'X\'`'
);
pref.checked +
"`: use either `'x'`, or `'X'`"
)
}
visit(tree, 'listItem', visitor);
visit(tree, 'listItem', visitor)
function visitor(node) {
var type;
var initial;
var final;
var stop;
var value;
var style;
var character;
var type
var initial
var final
var value
var style
var character
var reason
/* Exit early for items without checkbox. */
if (node.checked !== Boolean(node.checked) || generated(node)) {
return;
if (typeof node.checked !== 'boolean' || generated(node)) {
return
}
type = node.checked ? 'checked' : 'unchecked';
type = types[node.checked]
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;
/* For a checkbox to be parsed, it must be followed by a white space. */
value = contents
.slice(initial, final)
.trimRight()
.slice(0, -1)
/* 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 = pref[type]
/* The checkbox character is behind a square
* bracket. */
character = value.charAt(value.length - 1);
style = preferred[type];
if (style) {
if (character !== style) {
reason =
type.charAt(0).toUpperCase() +
type.slice(1) +
' checkboxes should use `' +
style +
'` as a marker'
if (style === null) {
preferred[type] = character;
} else if (character !== style) {
stop = initial + value.length;
file.message(
type.charAt(0).toUpperCase() + type.slice(1) +
' checkboxes should use `' + style + '` as a marker',
{
start: location.toPosition(stop - 1),
end: location.toPosition(stop)
}
);
file.message(reason, {
start: location.toPosition(initial + value.length - 1),
end: location.toPosition(initial + value.length)
})
}
} else {
pref[type] = character
}
}
}

View File

@ -27,56 +27,58 @@
* 4:7-4:10: Checkboxes should be followed by a single character
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var vfileLocation = require('vfile-location')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:checkbox-content-indent', checkboxContentIndent);
module.exports = rule(
'remark-lint:checkbox-content-indent',
checkboxContentIndent
)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
var reason = 'Checkboxes should be followed by a single character'
function checkboxContentIndent(tree, file) {
var contents = file.toString();
var location = vfileLocation(file);
var contents = String(file)
var location = vfileLocation(file)
visit(tree, 'listItem', visitor);
visit(tree, 'listItem', visitor)
function visitor(node) {
var initial;
var final;
var value;
var initial
var final
var value
/* Exit early for items without checkbox. */
if (node.checked !== Boolean(node.checked) || generated(node)) {
return;
if (typeof node.checked !== 'boolean' || generated(node)) {
return
}
initial = start(node).offset;
initial = start(node).offset
/* istanbul ignore next - hard to test, couldnt find a case. */
final = (node.children.length ? start(node.children[0]) : end(node)).offset;
final = (node.children.length ? start(node.children[0]) : end(node)).offset
while (/[^\S\n]/.test(contents.charAt(final))) {
final++;
final++
}
/* For a checkbox to be parsed, it must be followed
* by a white space. */
value = contents.slice(initial, final);
value = contents.slice(initial, final)
value = value.slice(value.indexOf(']') + 1)
value = value.slice(value.indexOf(']') + 1);
if (value.length === 1) {
return;
if (value.length !== 1) {
file.message(reason, {
start: location.toPosition(final - value.length + 1),
end: location.toPosition(final)
})
}
file.message('Checkboxes should be followed by a single character', {
start: location.toPosition(final - value.length + 1),
end: location.toPosition(final)
});
}
}

View File

@ -91,65 +91,58 @@
* 1:1: Invalid code block style `invalid`: use either `'consistent'`, `'fenced'`, or `'indented'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:code-block-style', codeBlockStyle);
module.exports = rule('remark-lint:code-block-style', codeBlockStyle)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
var STYLES = {
null: true,
fenced: true,
indented: true
};
var styles = {null: true, fenced: true, indented: true}
function codeBlockStyle(tree, file, preferred) {
var contents = file.toString();
function codeBlockStyle(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (STYLES[preferred] !== true) {
file.fail('Invalid code block style `' + preferred + '`: use either `\'consistent\'`, `\'fenced\'`, or `\'indented\'`');
if (styles[pref] !== true) {
file.fail(
'Invalid code block style `' +
pref +
"`: use either `'consistent'`, `'fenced'`, or `'indented'`"
)
}
visit(tree, 'code', visitor);
visit(tree, 'code', visitor)
function visitor(node) {
var current = check(node);
var current = check(node)
if (!current) {
return;
}
if (!preferred) {
preferred = current;
} else if (preferred !== current) {
file.message('Code blocks should be ' + preferred, node);
if (current) {
if (!pref) {
pref = current
} else if (pref !== current) {
file.message('Code blocks should be ' + pref, node)
}
}
}
/* Get the style of `node`. */
function check(node) {
var initial = start(node).offset;
var final = end(node).offset;
var initial = start(node).offset
var final = end(node).offset
if (generated(node)) {
return null;
return null
}
if (
node.lang ||
/^\s*([~`])\1{2,}/.test(contents.slice(initial, final))
) {
return 'fenced';
}
return 'indented';
return node.lang || /^\s*([~`])\1{2,}/.test(contents.slice(initial, final))
? 'fenced'
: 'indented'
}
}

View File

@ -19,38 +19,36 @@
* 1:1-1:47: Do not use upper-case characters in definition labels
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:definition-case', definitionCase);
module.exports = rule('remark-lint:definition-case', definitionCase)
var LABEL = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/;
var label = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/
var reason = 'Do not use upper-case characters in definition labels'
function definitionCase(tree, file) {
var contents = file.toString();
var contents = String(file)
visit(tree, 'definition', validate);
visit(tree, 'footnoteDefinition', validate);
visit(tree, ['definition', 'footnoteDefinition'], validate)
/* Validate a node, either a normal definition or
* a footnote definition. */
function validate(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var label;
var start = position.start(node).offset
var end = position.end(node).offset
var value
if (generated(node)) {
return;
}
if (!generated(node)) {
value = contents.slice(start, end).match(label)[1]
label = contents.slice(start, end).match(LABEL)[1];
if (label !== label.toLowerCase()) {
file.message('Do not use upper-case characters in definition labels', node);
if (value !== value.toLowerCase()) {
file.message(reason, node)
}
}
}
}

View File

@ -19,36 +19,32 @@
* 1:1-1:57: Do not use consecutive white-space in definition labels
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:definition-spacing', definitionSpacing);
module.exports = rule('remark-lint:definition-spacing', definitionSpacing)
var LABEL = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/;
var label = /^\s*\[((?:\\[\s\S]|[^[\]])+)]/
var reason = 'Do not use consecutive white-space in definition labels'
function definitionSpacing(tree, file) {
var contents = file.toString();
var contents = String(file)
visit(tree, 'definition', validate);
visit(tree, 'footnoteDefinition', validate);
visit(tree, ['definition', 'footnoteDefinition'], validate)
function validate(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var label;
var start = position.start(node).offset
var end = position.end(node).offset
if (generated(node)) {
return;
}
label = contents.slice(start, end).match(LABEL)[1];
if (/[ \t\n]{2,}/.test(label)) {
file.message('Do not use consecutive white-space in definition labels', node);
if (
!generated(node) &&
/[ \t\n]{2,}/.test(contents.slice(start, end).match(label)[1])
) {
file.message(reason, node)
}
}
}

View File

@ -59,43 +59,45 @@
* 1:1: Invalid emphasis marker `invalid`: use either `'consistent'`, `'*'`, or `'_'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:emphasis-marker', emphasisMarker);
module.exports = rule('remark-lint:emphasis-marker', emphasisMarker)
var MARKERS = {
'*': true,
_: true,
null: true
};
var markers = {null: true, '*': true, _: true}
function emphasisMarker(tree, file, preferred) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
function emphasisMarker(tree, file, pref) {
var contents = String(file)
if (MARKERS[preferred] !== true) {
file.fail('Invalid emphasis marker `' + preferred + '`: use either `\'consistent\'`, `\'*\'`, or `\'_\'`');
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (markers[pref] !== true) {
file.fail(
'Invalid emphasis marker `' +
pref +
"`: use either `'consistent'`, `'*'`, or `'_'`"
)
}
visit(tree, 'emphasis', visitor);
visit(tree, 'emphasis', visitor)
function visitor(node) {
var marker = file.toString().charAt(position.start(node).offset);
var marker
if (generated(node)) {
return;
}
if (!generated(node)) {
marker = contents.charAt(position.start(node).offset)
if (preferred) {
if (marker !== preferred) {
file.message('Emphasis should use `' + preferred + '` as a marker', node);
if (pref) {
if (marker !== pref) {
file.message('Emphasis should use `' + pref + '` as a marker', node)
}
} else {
pref = marker
}
} else {
preferred = marker;
}
}
}

View File

@ -64,48 +64,53 @@
* 1:1-3:4: Invalid code-language flag
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:fenced-code-flag', fencedCodeFlag);
module.exports = rule('remark-lint:fenced-code-flag', fencedCodeFlag)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function fencedCodeFlag(ast, file, preferred) {
var contents = file.toString();
var allowEmpty = false;
var flags = [];
var fence = /^ {0,3}([~`])\1{2,}/
var reasonInvalid = 'Invalid code-language flag'
var reasonMissing = 'Missing code-language flag'
if (typeof preferred === 'object' && !('length' in preferred)) {
allowEmpty = Boolean(preferred.allowEmpty);
function fencedCodeFlag(tree, file, pref) {
var contents = String(file)
var allowEmpty = false
var flags = []
preferred = preferred.flags;
if (typeof pref === 'object' && !('length' in pref)) {
allowEmpty = Boolean(pref.allowEmpty)
pref = pref.flags
}
if (typeof preferred === 'object' && 'length' in preferred) {
flags = String(preferred).split(',');
if (typeof pref === 'object' && 'length' in pref) {
flags = String(pref).split(',')
}
visit(ast, 'code', visitor);
visit(tree, 'code', visitor)
function visitor(node) {
var value = contents.slice(start(node).offset, end(node).offset);
var value
if (generated(node)) {
return;
}
if (!generated(node)) {
if (node.lang) {
if (flags.length !== 0 && flags.indexOf(node.lang) === -1) {
file.message(reasonInvalid, node)
}
} else {
value = contents.slice(start(node).offset, end(node).offset)
if (node.lang) {
if (flags.length !== 0 && flags.indexOf(node.lang) === -1) {
file.message('Invalid code-language flag', node);
if (!allowEmpty && fence.test(value)) {
file.message(reasonMissing, node)
}
}
} else if (/^ {0,3}([~`])\1{2,}/.test(value) && !allowEmpty) {
file.message('Missing code-language flag', node);
}
}
}

View File

@ -66,52 +66,58 @@
* 1:1: Invalid fenced code marker `!`: use either `'consistent'`, `` '`' ``, or `'~'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:fenced-code-marker', fencedCodeMarker);
module.exports = rule('remark-lint:fenced-code-marker', fencedCodeMarker)
var MARKERS = {
var markers = {
'`': true,
'~': true,
null: true
};
}
function fencedCodeMarker(ast, file, preferred) {
var contents = file.toString();
function fencedCodeMarker(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (MARKERS[preferred] !== true) {
file.fail('Invalid fenced code marker `' + preferred + '`: use either `\'consistent\'`, `` \'`\' ``, or `\'~\'`');
if (markers[pref] !== true) {
file.fail(
'Invalid fenced code marker `' +
pref +
"`: use either `'consistent'`, `` '`' ``, or `'~'`"
)
}
visit(ast, 'code', visitor);
visit(tree, 'code', visitor)
function visitor(node) {
var marker = contents.substr(position.start(node).offset, 4);
var marker
if (generated(node)) {
return;
}
if (!generated(node)) {
marker = contents
.substr(position.start(node).offset, 4)
.trimLeft()
.charAt(0)
marker = marker.trimLeft().charAt(0);
/* Ignore unfenced code blocks. */
if (MARKERS[marker] !== true) {
return;
}
if (preferred) {
if (marker !== preferred) {
file.message('Fenced code should use ' + preferred + ' as a marker', node);
/* Ignore unfenced code blocks. */
if (markers[marker] === true) {
if (pref) {
if (marker !== pref) {
file.message(
'Fenced code should use ' + pref + ' as a marker',
node
)
}
} else {
pref = marker
}
}
} else {
preferred = marker;
}
}
}

View File

@ -22,22 +22,18 @@
* @example {"name": "readme.mkd", "setting": "mkd"}
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:file-extension', fileExtension);
module.exports = rule('remark-lint:file-extension', fileExtension)
function fileExtension(ast, file, preferred) {
var ext = file.extname;
function fileExtension(tree, file, pref) {
var ext = file.extname
if (ext) {
ext = ext.slice(1);
}
pref = typeof pref === 'string' ? pref : 'md'
preferred = typeof preferred === 'string' ? preferred : 'md';
if (ext && ext !== preferred) {
file.message('Invalid extension: use `' + preferred + '`');
if (ext && ext.slice(1) !== pref) {
file.message('Invalid extension: use `' + pref + '`')
}
}

View File

@ -25,36 +25,41 @@
* 3:1-3:47: Move definitions to the end of the file (after the node at line `5`)
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:final-definition', finalDefinition);
module.exports = rule('remark-lint:final-definition', finalDefinition)
var start = position.start;
var start = position.start
function finalDefinition(ast, file) {
var last = null;
function finalDefinition(tree, file) {
var last = null
visit(ast, visitor, true);
visit(tree, visitor, true)
function visitor(node) {
var line = start(node).line;
var line = start(node).line
/* Ignore generated nodes. */
if (node.type === 'root' || generated(node)) {
return;
return
}
if (node.type === 'definition') {
if (last !== null && last > line) {
file.message('Move definitions to the end of the file (after the node at line `' + last + '`)', node);
file.message(
'Move definitions to the end of the file (after the node at line `' +
last +
'`)',
node
)
}
} else if (last === null) {
last = line;
last = line
}
}
}

View File

@ -49,17 +49,17 @@
* ```
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:final-newline', finalNewline);
module.exports = rule('remark-lint:final-newline', finalNewline)
function finalNewline(ast, file) {
var contents = file.toString();
var last = contents.length - 1;
function finalNewline(tree, file) {
var contents = String(file)
var last = contents.length - 1
if (last > -1 && contents.charAt(last) !== '\n') {
file.message('Missing newline character at end of file');
file.message('Missing newline character at end of file')
}
}

View File

@ -77,45 +77,43 @@
* 1:1-1:14: First heading level should be `2`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:first-heading-level', firstHeadingLevel);
module.exports = rule('remark-lint:first-heading-level', firstHeadingLevel)
var re = /<h([1-6])/;
var re = /<h([1-6])/
function firstHeadingLevel(tree, file, preferred) {
var style = preferred && preferred !== true ? preferred : 1;
function firstHeadingLevel(tree, file, pref) {
var style = pref && pref !== true ? pref : 1
visit(tree, visitor);
visit(tree, visitor)
function visitor(node) {
var depth;
var depth
if (generated(node)) {
return;
}
if (node.type === 'heading') {
depth = node.depth;
} else if (node.type === 'html') {
depth = infer(node);
}
if (depth !== undefined) {
if (depth !== style) {
file.message('First heading level should be `' + style + '`', node);
if (!generated(node)) {
if (node.type === 'heading') {
depth = node.depth
} else if (node.type === 'html') {
depth = infer(node)
}
return false;
if (depth !== undefined) {
if (depth !== style) {
file.message('First heading level should be `' + style + '`', node)
}
return visit.EXIT
}
}
}
}
function infer(node) {
var results = node.value.match(re);
return results ? Number(results[1]) : undefined;
var results = node.value.match(re)
return results ? Number(results[1]) : undefined
}

View File

@ -21,33 +21,34 @@
* 1:12-2:1: Use two spaces for hard line breaks
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:hard-break-spaces', hardBreakSpaces);
module.exports = rule('remark-lint:hard-break-spaces', hardBreakSpaces)
function hardBreakSpaces(ast, file) {
var contents = file.toString();
var reason = 'Use two spaces for hard line breaks'
visit(ast, 'break', visitor);
function hardBreakSpaces(tree, file) {
var contents = String(file)
visit(tree, 'break', visitor)
function visitor(node) {
var start = position.start(node).offset;
var end = position.end(node).offset;
var value;
var value
if (generated(node)) {
return;
}
if (!generated(node)) {
value = contents
.slice(position.start(node).offset, position.end(node).offset)
.split('\n', 1)[0]
.replace(/\r$/, '')
value = contents.slice(start, end).split('\n', 1)[0].replace(/\r$/, '');
if (value.length > 2) {
file.message('Use two spaces for hard line breaks', node);
if (value.length > 2) {
file.message(reason, node)
}
}
}
}

View File

@ -23,30 +23,32 @@
* 3:1-3:10: Heading levels should increment by one level at a time
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:heading-increment', headingIncrement);
module.exports = rule('remark-lint:heading-increment', headingIncrement)
function headingIncrement(ast, file) {
var prev = null;
var reason = 'Heading levels should increment by one level at a time'
visit(ast, 'heading', visitor);
function headingIncrement(tree, file) {
var prev = null
visit(tree, 'heading', visitor)
function visitor(node) {
var depth = node.depth;
var depth
if (generated(node)) {
return;
if (!generated(node)) {
depth = node.depth
if (prev && depth > prev + 1) {
file.message(reason, node)
}
prev = depth
}
if (prev && depth > prev + 1) {
file.message('Heading levels should increment by one level at a time', node);
}
prev = depth;
}
}

View File

@ -64,33 +64,31 @@
* 6:1-6:13: Headings should use setext
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var style = require('mdast-util-heading-style');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var style = require('mdast-util-heading-style')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:heading-style', headingStyle);
module.exports = rule('remark-lint:heading-style', headingStyle)
var TYPES = ['atx', 'atx-closed', 'setext'];
var types = ['atx', 'atx-closed', 'setext']
function headingStyle(ast, file, preferred) {
preferred = TYPES.indexOf(preferred) === -1 ? null : preferred;
function headingStyle(tree, file, pref) {
pref = types.indexOf(pref) === -1 ? null : pref
visit(ast, 'heading', visitor);
visit(tree, 'heading', visitor)
function visitor(node) {
if (generated(node)) {
return;
}
if (preferred) {
if (style(node, preferred) !== preferred) {
file.message('Headings should use ' + preferred, node);
if (!generated(node)) {
if (pref) {
if (style(node, pref) !== pref) {
file.message('Headings should use ' + pref, node)
}
} else {
pref = style(node, pref)
}
} else {
preferred = style(node, preferred);
}
}
}

View File

@ -45,46 +45,49 @@
* 1:6: Expected linebreaks to be windows (`\r\n`), not unix (`\n`)
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var location = require('vfile-location');
var rule = require('unified-lint-rule')
var location = require('vfile-location')
module.exports = rule('remark-lint:linebreak-style', linebreakStyle);
module.exports = rule('remark-lint:linebreak-style', linebreakStyle)
var sequences = {
unix: '\n',
windows: '\r\n'
};
var sequences = {unix: '\n', windows: '\r\n'}
var escaped = {unix: '\\n', windows: '\\r\\n'}
var types = {true: 'windows', false: 'unix'}
var escaped = {
unix: '\\n',
windows: '\\r\\n'
};
function linebreakStyle(tree, file, pref) {
var content = String(file)
var position = location(content).toPosition
var index = content.indexOf('\n')
var type
var reason
function linebreakStyle(ast, file, preferred) {
var content = String(file);
var position = location(content).toPosition;
var index = content.indexOf('\n');
var type;
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
while (index !== -1) {
type = content.charAt(index - 1) === '\r' ? 'windows' : 'unix';
type = types[content.charAt(index - 1) === '\r']
if (preferred) {
if (sequences[preferred] !== sequences[type]) {
file.message(
'Expected linebreaks to be ' + preferred + ' (`' + escaped[preferred] + '`), ' +
'not ' + type + ' (`' + escaped[type] + '`)',
position(index)
);
if (pref) {
if (sequences[pref] !== sequences[type]) {
reason =
'Expected linebreaks to be ' +
pref +
' (`' +
escaped[pref] +
'`), ' +
'not ' +
type +
' (`' +
escaped[type] +
'`)'
file.message(reason, position(index))
}
} else {
preferred = type;
pref = type
}
index = content.indexOf('\n', index + 1);
index = content.indexOf('\n', index + 1)
}
}

View File

@ -67,77 +67,75 @@
* 1:1: Invalid link title style marker `.`: use either `'consistent'`, `'"'`, `'\''`, or `'()'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var vfileLocation = require('vfile-location')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:link-title-style', linkTitleStyle);
module.exports = rule('remark-lint:link-title-style', linkTitleStyle)
var end = position.end;
var end = position.end
var MARKERS = {
'"': true,
'\'': true,
')': true,
null: true
};
var markers = {'"': true, "'": true, ')': true, null: true}
function linkTitleStyle(ast, file, preferred) {
var contents = file.toString();
var location = vfileLocation(file);
function linkTitleStyle(tree, file, pref) {
var contents = String(file)
var location = vfileLocation(file)
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
pref = pref === '()' || pref === '(' ? ')' : pref
if (preferred === '()' || preferred === '(') {
preferred = ')';
if (markers[pref] !== true) {
file.fail(
'Invalid link title style marker `' +
pref +
"`: use either `'consistent'`, `'\"'`, `'\\''`, or `'()'`"
)
}
if (MARKERS[preferred] !== true) {
file.fail('Invalid link title style marker `' + preferred + '`: use either `\'consistent\'`, `\'"\'`, `\'\\\'\'`, or `\'()\'`');
}
visit(ast, 'link', validate);
visit(ast, 'image', validate);
visit(ast, 'definition', validate);
visit(tree, ['link', 'image', 'definition'], validate)
function validate(node) {
var last = end(node).offset - 1;
var character;
var pos;
var last = end(node).offset - 1
var character
var reason
if (generated(node)) {
return;
return
}
if (node.type !== 'definition') {
last--;
last--
}
while (last) {
character = contents.charAt(last);
character = contents.charAt(last)
/* istanbul ignore if - remark before 8.0.0 */
if (/\s/.test(character)) {
last--;
last--
} else {
break;
break
}
}
/* Not a title. */
if (!(character in MARKERS)) {
return;
}
/* Skip non-titles. */
if (character in markers) {
if (pref) {
if (pref !== character) {
reason =
'Titles should use `' +
(pref === ')' ? '()' : pref) +
'` as a quote'
if (!preferred) {
preferred = character;
} else if (preferred !== character) {
pos = location.toPosition(last + 1);
file.message('Titles should use `' + (preferred === ')' ? '()' : preferred) + '` as a quote', pos);
file.message(reason, location.toPosition(last + 1))
}
} else {
pref = character
}
}
}
}

View File

@ -34,48 +34,52 @@
* 4:3: Incorrect indentation before bullet: remove 1 space
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var plural = require('plur');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var plural = require('plur')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:list-item-bullet-indent', listItemBulletIndent);
module.exports = rule(
'remark-lint:list-item-bullet-indent',
listItemBulletIndent
)
var start = position.start;
var start = position.start
function listItemBulletIndent(ast, file) {
var contents = file.toString();
function listItemBulletIndent(tree, file) {
var contents = String(file)
visit(ast, 'list', visitor);
visit(tree, 'list', visitor)
function visitor(node) {
var items = node.children;
items.forEach(visitItems);
node.children.forEach(visitItems)
}
function visitItems(item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var indent;
var final
var indent
var reason
if (generated(item)) {
return;
}
if (!generated(item)) {
final = start(item.children[0])
indent = contents.slice(start(item).offset, final.offset).match(/^\s*/)[0]
.length
indent = contents.slice(initial, final).match(/^\s*/)[0].length;
if (indent !== 0) {
reason =
'Incorrect indentation before bullet: remove ' +
indent +
' ' +
plural('space', indent)
if (indent !== 0) {
initial = start(head);
file.message('Incorrect indentation before bullet: remove ' + indent + ' ' + plural('space', indent), {
line: initial.line,
column: initial.column - indent
});
file.message(reason, {
line: final.line,
column: final.column - indent
})
}
}
}
}

View File

@ -21,39 +21,46 @@
* 2:5: Dont use mixed indentation for children, remove 1 space
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var plural = require('plur');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var plural = require('plur')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:list-item-content-indent', listItemContentIndent);
module.exports = rule(
'remark-lint:list-item-content-indent',
listItemContentIndent
)
var start = position.start;
var start = position.start
function listItemContentIndent(ast, file) {
var contents = file.toString();
function listItemContentIndent(tree, file) {
var contents = String(file)
visit(ast, 'listItem', visitor);
visit(tree, 'listItem', visitor)
function visitor(node) {
var style;
var style
node.children.forEach(visitItem);
node.children.forEach(visitItem)
function visitItem(item, index) {
var begin = start(item);
var column = begin.column;
var char;
var diff;
var word;
var begin
var column
var char
var diff
var absDiff
var reason
if (generated(item)) {
return;
return
}
begin = start(item)
column = begin.column
/* Get indentation for the first child.
* Only the first item can have a checkbox,
* so here we remove that from the column. */
@ -61,37 +68,39 @@ function listItemContentIndent(ast, file) {
/* 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 (typeof node.checked === 'boolean') {
char = begin.offset - 1
while (contents.charAt(char) !== '[') {
char--;
char--
}
column -= begin.offset - char;
column -= begin.offset - char
}
style = column;
style = column
return;
return
}
/* Warn for violating children. */
if (column !== style) {
diff = style - column;
/* istanbul ignore next - hard to test, I couldnt find it at least. */
word = diff > 0 ? 'add' : 'remove';
diff = style - column
absDiff = Math.abs(diff)
diff = Math.abs(diff);
reason =
'Dont use mixed indentation for children, ' +
/* istanbul ignore next - hard to test, I couldnt find it at least. */
(diff > 0 ? 'add' : 'remove') +
' ' +
absDiff +
' ' +
plural('space', absDiff)
file.message(
'Dont use mixed indentation for children, ' + word +
' ' + diff + ' ' + plural('space', diff),
{
line: start(item).line,
column: column
}
);
file.message(reason, {
line: start(item).line,
column: column
})
}
}
}

View File

@ -101,82 +101,76 @@
* 1:1: Invalid list-item indent style `invalid`: use either `'tab-size'`, `'space'`, or `'mixed'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var plural = require('plur');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var plural = require('plur')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:list-item-indent', listItemIndent);
module.exports = rule('remark-lint:list-item-indent', listItemIndent)
var start = position.start;
var start = position.start
var STYLES = {
'tab-size': true,
mixed: true,
space: true
};
var styles = {'tab-size': true, mixed: true, space: true}
function listItemIndent(ast, file, preferred) {
var contents = file.toString();
function listItemIndent(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred === 'string' ? preferred : 'tab-size';
pref = typeof pref === 'string' ? pref : 'tab-size'
if (STYLES[preferred] !== true) {
file.fail('Invalid list-item indent style `' + preferred + '`: use either `\'tab-size\'`, `\'space\'`, or `\'mixed\'`');
if (styles[pref] !== true) {
file.fail(
'Invalid list-item indent style `' +
pref +
"`: use either `'tab-size'`, `'space'`, or `'mixed'`"
)
}
visit(ast, 'list', visitor);
visit(tree, 'list', visitor)
function visitor(node) {
var items = node.children;
var loose = node.loose
if (generated(node)) {
return;
if (!generated(node)) {
node.children.forEach(visitItem)
}
items.forEach(visitItem);
function visitItem(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;
var head = item.children[0]
var final = start(head)
var marker
var bulletSize
var style
var diff
var absDiff
var reason
marker = contents.slice(initial, final);
marker = contents
.slice(start(item).offset, final.offset)
.replace(/\[[x ]?]\s*$/i, '')
/* Support checkboxes. */
marker = marker.replace(/\[[x ]?]\s*$/i, '');
bulletSize = marker.trimRight().length
bulletSize = marker.trimRight().length;
tab = Math.ceil(bulletSize / 4) * 4;
style =
pref === 'tab-size' || (pref === 'mixed' && loose)
? Math.ceil(bulletSize / 4) * 4
: bulletSize + 1
if (preferred === 'tab-size') {
shouldBe = tab;
} else if (preferred === 'space') {
shouldBe = bulletSize + 1;
} else {
shouldBe = node.loose ? tab : bulletSize + 1;
}
if (marker.length !== style) {
diff = style - marker.length
absDiff = Math.abs(diff)
if (marker.length !== shouldBe) {
diff = shouldBe - marker.length;
word = diff > 0 ? 'add' : 'remove';
reason =
'Incorrect list-item indent: ' +
(diff > 0 ? 'add' : 'remove') +
' ' +
absDiff +
' ' +
plural('space', absDiff)
diff = Math.abs(diff);
file.message(
'Incorrect list-item indent: ' + word +
' ' + diff + ' ' + plural('space', diff),
start(head)
);
file.message(reason, final)
}
}
}

View File

@ -107,103 +107,90 @@
* 15:1-16:1: Extraneous new line after list item
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:list-item-spacing', listItemSpacing);
module.exports = rule('remark-lint:list-item-spacing', listItemSpacing)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function listItemSpacing(ast, file, preferred) {
var blanks = Boolean(
preferred &&
typeof preferred === 'object' &&
preferred.checkBlanks
);
var reasonLoose = 'Missing new line after list item'
var reasonTight = 'Extraneous new line after list item'
visit(ast, 'list', visitor);
function listItemSpacing(tree, file, pref) {
var blanks = pref && typeof pref === 'object' && Boolean(pref.checkBlanks)
var fn = blanks ? inferBlankLine : inferMultiline
visit(tree, 'list', visitor)
function visitor(node) {
var items = node.children;
var isTightList = true;
var indent = start(node).column;
var type;
var tight = true
var indent
var children
var length
var index
var child
var next
if (generated(node)) {
return;
}
items.forEach(infer);
type = isTightList ? 'tight' : 'loose';
items.forEach(warn);
function infer(item) {
var fn = blanks ? inferBlankLine : inferMultiline;
if (fn(item)) {
isTightList = false;
}
}
function inferBlankLine(item) {
var children = item.children;
var length = children.length;
var index = 0;
var child = children[index];
var next;
if (!generated(node)) {
children = node.children
length = children.length
index = -1
while (++index < length) {
next = children[index];
/* All children in `listItem`s are block. */
if ((start(next).line - end(child).line) > 1) {
return true;
if (fn(children[index])) {
tight = false
break
}
child = next;
}
return false;
}
indent = start(node).column
child = children[0]
index = 0
function inferMultiline(item) {
var content = item.children;
var head = content[0];
var tail = content[content.length - 1];
return (end(tail).line - start(head).line) > 0;
}
while (++index < length) {
next = children[index]
function warn(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.message('Missing new line after list item', {
start: end(item),
if (end(child).column > indent !== tight) {
file.message(tight ? reasonTight : reasonLoose, {
start: end(child),
end: start(next)
});
} else {
file.message('Extraneous new line after list item', {
start: end(item),
end: start(next)
});
})
}
child = next
}
}
}
}
function inferBlankLine(node) {
var children = node.children
var child = children[0]
var length = children.length
var index = 0
var next
while (++index < length) {
next = children[index]
/* All children in `listItem`s are block. */
if (start(next).line - end(child).line > 1) {
return true
}
child = next
}
return false
}
function inferMultiline(node) {
var children = node.children
return end(children[children.length - 1]).line - start(children[0]).line > 0
}

View File

@ -25,27 +25,26 @@
* 1:1-1:52: Use headings shorter than `40`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:maximum-heading-length', maximumHeadingLength);
module.exports = rule(
'remark-lint:maximum-heading-length',
maximumHeadingLength
)
function maximumHeadingLength(ast, file, preferred) {
preferred = isNaN(preferred) || typeof preferred !== 'number' ? 60 : preferred;
function maximumHeadingLength(tree, file, pref) {
pref = typeof pref === 'number' && !isNaN(pref) ? pref : 60
visit(ast, 'heading', visitor);
visit(tree, 'heading', visitor)
function visitor(node) {
if (generated(node)) {
return;
}
if (toString(node).length > preferred) {
file.message('Use headings shorter than `' + preferred + '`', node);
if (!generated(node) && toString(node).length > pref) {
file.message('Use headings shorter than `' + pref + '`', node)
}
}
}

View File

@ -83,63 +83,38 @@
* 4:12: Line must be at most 10 characters
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:maximum-line-length', maximumLineLength);
module.exports = rule('remark-lint:maximum-line-length', maximumLineLength)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function maximumLineLength(ast, file, preferred) {
var style = preferred && preferred !== true ? preferred : 80;
var content = file.toString();
var matrix = content.split(/\r?\n/);
var index = -1;
var length = matrix.length;
var lineLength;
function maximumLineLength(tree, file, pref) {
var style = typeof pref === 'number' && !isNaN(pref) ? pref : 80
var content = String(file)
var lines = content.split(/\r?\n/)
var length = lines.length
var index = -1
var lineLength
/* Next, white list nodes which cannot be wrapped. */
visit(ast, ignore);
visit(tree, ['heading', 'table', 'code', 'definition'], ignore)
visit(tree, ['link', 'image'], validateLink)
visit(ast, 'link', validateLink);
visit(ast, 'image', validateLink);
/* Iterate over every line, and warn for
* violating lines. */
/* Iterate over every line, and warn for violating lines. */
while (++index < length) {
lineLength = matrix[index].length;
lineLength = lines[index].length
if (lineLength > style) {
file.message('Line must be at most ' + style + ' characters', {
line: index + 1,
column: lineLength + 1
});
}
}
function ignore(node) {
var applicable = isIgnored(node);
var initial = applicable && start(node).line;
var final = applicable && end(node).line;
if (!applicable || generated(node)) {
return;
}
whitelist(initial - 1, final);
}
/* Whitelist from `initial` to `final`, zero-based. */
function whitelist(initial, final) {
initial--;
while (++initial < final) {
matrix[initial] = '';
})
}
}
@ -148,21 +123,21 @@ function maximumLineLength(ast, file, preferred) {
* theres white-space after it, they are not
* whitelisted. */
function validateLink(node, pos, parent) {
var next = parent.children[pos + 1];
var initial = start(node);
var final = end(node);
var next = parent.children[pos + 1]
var initial
var final
/* Nothing to whitelist when generated. */
/* istanbul ignore if - Hard to test, as we only run this
* case on `position: true` */
/* istanbul ignore if - Nothing to whitelist when generated. */
if (generated(node)) {
return;
return
}
/* No whitelisting when starting after the border,
* or ending before it. */
initial = start(node)
final = end(node)
/* No whitelisting when starting after the border, or ending before it. */
if (initial.column > style || final.column < style) {
return;
return
}
/* No whitelisting when theres white-space after
@ -172,18 +147,23 @@ function maximumLineLength(ast, file, preferred) {
start(next).line === initial.line &&
(!next.value || /^(.+?[ \t].+?)/.test(next.value))
) {
return;
return
}
whitelist(initial.line - 1, final.line);
whitelist(initial.line - 1, final.line)
}
function ignore(node) {
/* istanbul ignore else - Hard to test, as we only run this case on `position: true` */
if (!generated(node)) {
whitelist(start(node).line - 1, end(node).line)
}
}
/* Whitelist from `initial` to `final`, zero-based. */
function whitelist(initial, final) {
while (initial < final) {
lines[initial++] = ''
}
}
}
/* Check if `node` is applicable, as in, if it should be
* ignored. */
function isIgnored(node) {
return node.type === 'heading' ||
node.type === 'table' ||
node.type === 'code' ||
node.type === 'definition';
}

View File

@ -31,38 +31,44 @@
* 1:1-1:14: All automatic links must start with a protocol
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:no-auto-link-without-protocol', noAutoLinkWithoutProtocol);
module.exports = rule(
'remark-lint:no-auto-link-without-protocol',
noAutoLinkWithoutProtocol
)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
/* Protocol expression. See:
* http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax */
var PROTOCOL = /^[a-z][a-z+.-]+:\/?/i;
var protocol = /^[a-z][a-z+.-]+:\/?/i
function noAutoLinkWithoutProtocol(ast, file) {
visit(ast, 'link', visitor);
var reason = 'All automatic links must start with a protocol'
function noAutoLinkWithoutProtocol(tree, file) {
visit(tree, 'link', visitor)
function visitor(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 children
if (generated(node)) {
return;
}
if (!generated(node)) {
children = node.children
if (initial === head - 1 && final === tail + 1 && !PROTOCOL.test(toString(node))) {
file.message('All automatic links must start with a protocol', node);
if (
start(node).column === start(children[0]).column - 1 &&
end(node).column === end(children[children.length - 1]).column + 1 &&
!protocol.test(toString(node))
) {
file.message(reason, node)
}
}
}
}

View File

@ -31,58 +31,64 @@
* 2:1: Missing marker in blockquote
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var vfileLocation = require('vfile-location');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var vfileLocation = require('vfile-location')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-blockquote-without-marker', noBlockquoteWithoutMarker);
module.exports = rule(
'remark-lint:no-blockquote-without-marker',
noBlockquoteWithoutMarker
)
function noBlockquoteWithoutMarker(ast, file) {
var contents = file.toString();
var location = vfileLocation(file);
var last = contents.length;
var reason = 'Missing marker in blockquote'
visit(ast, 'blockquote', visitor);
function noBlockquoteWithoutMarker(tree, file) {
var contents = String(file)
var location = vfileLocation(file)
var last = contents.length
visit(tree, 'blockquote', visitor)
function visitor(node) {
var start = position.start(node).line;
var indent = node.position && node.position.indent;
var indent = node.position && node.position.indent
var start
var length
var index
var line
var offset
var character
var pos
if (generated(node) || !indent || indent.length === 0) {
return;
return
}
indent.forEach(eachLine);
start = position.start(node).line
length = indent.length
index = -1
function eachLine(column, n) {
var character;
var line = start + n + 1;
var offset = location.toOffset({
line: line,
column: column
}) - 1;
while (++index < length) {
line = start + index + 1
pos = {line: line, column: indent[index]}
offset = location.toOffset(pos) - 1
while (++offset < last) {
character = contents.charAt(offset);
character = contents.charAt(offset)
if (character === '>') {
return;
break
}
/* istanbul ignore else - just for safety */
if (character !== ' ' && character !== '\t') {
break;
file.message(reason, pos)
break
}
}
file.message('Missing marker in blockquote', {
line: line,
column: column
});
}
}
}

View File

@ -31,84 +31,98 @@
*
* bravo();
*
* @example {"name": "empty-document.md"}
*
* @example {"name": "invalid.md", "label": "input"}
*
* Foo...
*
*
* ...Bar.
* ...Bar
*
*
*
* @example {"name": "invalid.md", "label": "output"}
*
* 4:1: Remove 1 line before node
* 4:7: Remove 2 lines after node
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var plural = require('plur');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var plural = require('plur')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-consecutive-blank-lines', noConsecutiveBlankLines);
module.exports = rule(
'remark-lint:no-consecutive-blank-lines',
noConsecutiveBlankLines
)
var MAX = 2;
function noConsecutiveBlankLines(ast, file) {
visit(ast, visitor);
function noConsecutiveBlankLines(tree, file) {
visit(tree, visitor)
function visitor(node) {
var children = node.children;
var head = children && children[0];
var tail = children && children[children.length - 1];
var children = node.children
var head
var tail
if (generated(node)) {
return;
}
if (!generated(node) && children) {
head = children[0]
if (head && !generated(head)) {
/* Compare parent and first child. */
compare(position.start(node), position.start(head), 0);
if (head && !generated(head)) {
/* Compare parent and first child. */
compare(position.start(node), position.start(head), 0)
/* Compare between each child. */
children.forEach(visitChild);
/* Compare between each child. */
children.forEach(visitChild)
/* Compare parent and last child. */
if (tail !== head && !generated(tail)) {
compare(position.end(node), position.end(tail), 1);
tail = children[children.length - 1]
/* Compare parent and last child. */
if (tail !== head && !generated(tail)) {
compare(position.end(node), position.end(tail), 1)
}
}
}
function visitChild(child, index) {
var prev = children[index - 1];
var max = MAX;
if (!prev || generated(prev) || 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 the difference between `start` and `end`,
* and warn when that difference exceeds `max`. */
function compare(start, end, max) {
var diff = end.line - start.line;
var word = diff > 0 ? 'before' : 'after';
var diff = end.line - start.line
var lines = Math.abs(diff) - max
var reason
diff = Math.abs(diff) - max;
if (lines > 0) {
reason =
'Remove ' +
lines +
' ' +
plural('line', lines) +
' ' +
(diff > 0 ? 'before' : 'after') +
' node'
if (diff > 0) {
file.message('Remove ' + diff + ' ' + plural('line', diff) + ' ' + word + ' node', end);
file.message(reason, end)
}
}
function visitChild(child, index, all) {
var prev = all[index - 1]
var max = 2
if (prev && !generated(prev) && !generated(child)) {
if (
(prev.type === 'list' && child.type === 'list') ||
(child.type === 'code' && prev.type === 'list' && !child.lang)
) {
max++
}
compare(position.end(prev), position.start(child), max)
}
}
}

View File

@ -59,6 +59,12 @@ Paragraph.
No messages.
##### `empty-document.md`
###### Out
No messages.
##### `invalid.md`
###### In
@ -69,13 +75,16 @@ Note: `␊` represents a line feed.
Foo...
...Bar.
...Bar
```
###### Out
```text
4:1: Remove 1 line before node
4:7: Remove 2 lines after node
```
## Install

View File

@ -21,39 +21,40 @@
* 2:1-2:11: Do not use definitions with the same identifier (1:1)
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var visit = require('unist-util-visit');
var rule = require('unified-lint-rule')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
var visit = require('unist-util-visit')
module.exports = rule('remark-lint:no-duplicate-definitions', noDuplicateDefinitions);
module.exports = rule(
'remark-lint:no-duplicate-definitions',
noDuplicateDefinitions
)
function noDuplicateDefinitions(ast, file) {
var map = {};
var reason = 'Do not use definitions with the same identifier'
visit(ast, 'definition', validate);
visit(ast, 'footnoteDefinition', validate);
function noDuplicateDefinitions(tree, file) {
var map = {}
visit(tree, ['definition', 'footnoteDefinition'], validate)
function validate(node) {
var duplicate = map[node.identifier];
var pos;
var identifier
var duplicate
var pos
if (generated(node)) {
return;
if (!generated(node)) {
identifier = node.identifier
duplicate = map[identifier]
if (duplicate && duplicate.type) {
pos = position.start(duplicate)
file.message(reason + ' (' + pos.line + ':' + pos.column + ')', node)
}
map[identifier] = node
}
if (duplicate && duplicate.type) {
pos = position.start(duplicate);
file.message(
'Do not use definitions with the same identifier (' +
pos.line + ':' + pos.column + ')',
node
);
}
map[node.identifier] = node;
}
}

View File

@ -38,39 +38,40 @@
* 5:1-5:9: Do not use headings with similar content per section (3:1)
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
var visit = require('unist-util-visit')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:no-duplicate-headings-in-section', noDuplicateHeadingsInSection);
module.exports = rule(
'remark-lint:no-duplicate-headings-in-section',
noDuplicateHeadingsInSection
)
var reason = 'Do not use headings with similar content per section'
function noDuplicateHeadingsInSection(tree, file) {
var stack = [{}];
var stack = [{}]
visit(tree, 'heading', visitor);
visit(tree, 'heading', visitor)
function visitor(node) {
var depth = node.depth;
var siblings = stack[depth - 1] || {};
var value = toString(node).toUpperCase();
var duplicate = siblings[value];
var pos;
var depth = node.depth
var siblings = stack[depth - 1] || {}
var value = toString(node).toUpperCase()
var duplicate = siblings[value]
var pos
stack = stack.slice(0, depth);
stack[depth] = {};
siblings[value] = node;
stack = stack.slice(0, depth)
stack[depth] = {}
siblings[value] = node
if (!generated(node) && duplicate && duplicate.type === 'heading') {
pos = position.start(duplicate);
file.message(
'Do not use headings with similar content per section (' +
pos.line + ':' + pos.column + ')',
node
);
pos = position.start(duplicate)
file.message(reason + ' (' + pos.line + ':' + pos.column + ')', node)
}
}
}

View File

@ -26,40 +26,39 @@
* 5:1-5:29: Do not use headings with similar content (3:1)
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var visit = require('unist-util-visit');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
var visit = require('unist-util-visit')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:no-duplicate-headings', noDuplicateHeadings);
module.exports = rule('remark-lint:no-duplicate-headings', noDuplicateHeadings)
function noDuplicateHeadings(ast, file) {
var map = {};
var reason = 'Do not use headings with similar content'
visit(ast, 'heading', visitor);
function noDuplicateHeadings(tree, file) {
var map = {}
visit(tree, 'heading', visitor)
function visitor(node) {
var value = toString(node).toUpperCase();
var duplicate = map[value];
var pos;
var value
var duplicate
var pos
if (generated(node)) {
return;
if (!generated(node)) {
value = toString(node).toUpperCase()
duplicate = map[value]
if (duplicate && duplicate.type === 'heading') {
pos = position.start(duplicate)
file.message(reason + ' (' + pos.line + ':' + pos.column + ')', node)
}
map[value] = node
}
if (duplicate && duplicate.type === 'heading') {
pos = position.start(duplicate);
file.message(
'Do not use headings with similar content (' +
pos.line + ':' + pos.column + ')',
node
);
}
map[value] = node;
}
}

View File

@ -29,35 +29,33 @@
* 5:1-5:8: Dont use emphasis to introduce a section, use a heading
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-emphasis-as-heading', noEmphasisAsHeading);
module.exports = rule('remark-lint:no-emphasis-as-heading', noEmphasisAsHeading)
function noEmphasisAsHeading(ast, file) {
visit(ast, 'paragraph', visitor);
var reason = 'Dont use emphasis to introduce a section, use a heading'
function noEmphasisAsHeading(tree, file) {
visit(tree, 'paragraph', visitor)
function visitor(node, index, parent) {
var children = node.children;
var child = children[0];
var prev = parent.children[index - 1];
var next = parent.children[index + 1];
if (generated(node)) {
return;
}
var head = node.children[0]
var prev = parent.children[index - 1]
var next = parent.children[index + 1]
if (
!generated(node) &&
(!prev || prev.type !== 'heading') &&
next &&
next.type === 'paragraph' &&
children.length === 1 &&
(child.type === 'emphasis' || child.type === 'strong')
node.children.length === 1 &&
(head.type === 'emphasis' || head.type === 'strong')
) {
file.message('Dont use emphasis to introduce a section, use a heading', node);
file.message(reason, node)
}
}
}

View File

@ -11,7 +11,7 @@
* [alpha](http://bravo.com).
*
* ![charlie](http://delta.com/echo.png "foxtrott").
*
*
* @example {"name": "invalid.md", "label": "input"}
*
* [golf]().
@ -24,22 +24,20 @@
* 3:1-3:11: Dont use images without URL
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-empty-url', noEmptyURL);
var types = ['link', 'image'];
module.exports = rule('remark-lint:no-empty-url', noEmptyURL)
function noEmptyURL(tree, file) {
visit(tree, visitor);
visit(tree, ['link', 'image'], visitor)
function visitor(node) {
if (types.indexOf(node.type) !== -1 && !generated(node) && !node.url) {
file.message('Dont use ' + node.type + 's without URL', node);
if (!generated(node) && !node.url) {
file.message('Dont use ' + node.type + 's without URL', node)
}
}
}

View File

@ -25,16 +25,16 @@
* 1:1: Do not start file names with `an`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:no-file-name-articles', noFileNameArticles);
module.exports = rule('remark-lint:no-file-name-articles', noFileNameArticles)
function noFileNameArticles(ast, file) {
var match = file.stem && file.stem.match(/^(the|teh|an?)\b/i);
function noFileNameArticles(tree, file) {
var match = file.stem && file.stem.match(/^(the|teh|an?)\b/i)
if (match) {
file.message('Do not start file names with `' + match[0] + '`');
file.message('Do not start file names with `' + match[0] + '`')
}
}

View File

@ -13,14 +13,19 @@
* 1:1: Do not use consecutive dashes in a file name
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:no-file-name-consecutive-dashes', noFileNameConsecutiveDashes);
module.exports = rule(
'remark-lint:no-file-name-consecutive-dashes',
noFileNameConsecutiveDashes
)
function noFileNameConsecutiveDashes(ast, file) {
var reason = 'Do not use consecutive dashes in a file name'
function noFileNameConsecutiveDashes(tree, file) {
if (file.stem && /-{2,}/.test(file.stem)) {
file.message('Do not use consecutive dashes in a file name');
file.message(reason)
}
}

View File

@ -31,23 +31,28 @@
* 1:1: Do not use ` ` in a file name
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:no-file-name-irregular-characters', noFileNameIrregularCharacters);
module.exports = rule(
'remark-lint:no-file-name-irregular-characters',
noFileNameIrregularCharacters
)
function noFileNameIrregularCharacters(ast, file, preferred) {
var expression = preferred || /[^\\.a-zA-Z0-9-]/;
var match;
var expression = /[^\\.a-zA-Z0-9-]/
if (typeof expression === 'string') {
expression = new RegExp('[^' + expression + ']');
function noFileNameIrregularCharacters(tree, file, pref) {
var style = pref || expression
var match
if (typeof style === 'string') {
style = new RegExp('[^' + style + ']')
}
match = file.stem && file.stem.match(expression);
match = file.stem && file.stem.match(style)
if (match) {
file.message('Do not use `' + match[0] + '` in a file name');
file.message('Do not use `' + match[0] + '` in a file name')
}
}

View File

@ -16,16 +16,21 @@
* 1:1: Do not mix casing in file names
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:no-file-name-mixed-case', noFileNameMixedCase);
module.exports = rule(
'remark-lint:no-file-name-mixed-case',
noFileNameMixedCase
)
function noFileNameMixedCase(ast, file) {
var name = file.stem;
var reason = 'Do not mix casing in file names'
function noFileNameMixedCase(tree, file) {
var name = file.stem
if (name && !(name === name.toLowerCase() || name === name.toUpperCase())) {
file.message('Do not mix casing in file names');
file.message(reason)
}
}

View File

@ -17,14 +17,19 @@
* 1:1: Do not use initial or final dashes in a file name
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var rule = require('unified-lint-rule')
module.exports = rule('remark-lint:no-file-name-outer-dashes', noFileNameOuterDashes);
module.exports = rule(
'remark-lint:no-file-name-outer-dashes',
noFileNameOuterDashes
)
function noFileNameOuterDashes(ast, file) {
var reason = 'Do not use initial or final dashes in a file name'
function noFileNameOuterDashes(tree, file) {
if (file.stem && /^-|-$/.test(file.stem)) {
file.message('Do not use initial or final dashes in a file name');
file.message(reason)
}
}

View File

@ -57,75 +57,85 @@
* 3:34: Remove 1 space after this headings content
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var style = require('mdast-util-heading-style');
var plural = require('plur');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var style = require('mdast-util-heading-style')
var plural = require('plur')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-heading-content-indent', noHeadingContentIndent);
module.exports = rule(
'remark-lint:no-heading-content-indent',
noHeadingContentIndent
)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function noHeadingContentIndent(ast, file) {
var contents = file.toString();
function noHeadingContentIndent(tree, file) {
var contents = String(file)
visit(ast, 'heading', visitor);
visit(tree, 'heading', visitor)
function visitor(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;
var depth
var children
var type
var head
var initial
var final
var diff
var absDiff
var index
var char
var reason
if (generated(node)) {
return;
return
}
depth = node.depth
children = node.children
type = style(node, 'atx')
if (type === 'atx' || type === 'atx-closed') {
initial = start(node);
index = initial.offset;
char = contents.charAt(index);
initial = start(node)
index = initial.offset
char = contents.charAt(index)
while (char && char !== '#') {
index++;
char = contents.charAt(index);
char = contents.charAt(++index)
}
/* istanbul ignore if - CR/LF bug: remarkjs/remark#195. */
if (!char) {
return;
return
}
index = depth + (index - initial.offset);
head = start(children[0]).column;
index = depth + (index - initial.offset)
head = start(children[0]).column
/* Ignore empty headings. */
if (!head) {
return;
return
}
diff = head - initial.column - 1 - index;
diff = head - initial.column - 1 - index
if (diff) {
word = diff > 0 ? 'Remove' : 'Add';
diff = Math.abs(diff);
absDiff = Math.abs(diff)
file.message(
word + ' ' + diff + ' ' + plural('space', diff) +
' before this headings content',
start(children[0])
);
reason =
(diff > 0 ? 'Remove' : 'Add') +
' ' +
absDiff +
' ' +
plural('space', absDiff) +
' before this headings content'
file.message(reason, start(children[0]))
}
}
@ -133,15 +143,18 @@ function noHeadingContentIndent(ast, file) {
* 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;
final = end(children[children.length - 1])
diff = end(node).column - final.column - 1 - depth
if (diff) {
file.message(
'Remove ' + diff + ' ' + plural('space', diff) +
' after this headings content',
final
);
reason =
'Remove ' +
diff +
' ' +
plural('space', diff) +
' after this headings content'
file.message(reason, final)
}
}
}

View File

@ -46,54 +46,57 @@
* 8:4: Remove 3 spaces before this heading
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var plural = require('plur');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var plural = require('plur')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-heading-indent', noHeadingIndent);
module.exports = rule('remark-lint:no-heading-indent', noHeadingIndent)
var start = position.start;
var start = position.start
function noHeadingIndent(ast, file) {
var contents = file.toString();
var length = contents.length;
function noHeadingIndent(tree, file) {
var contents = String(file)
var length = contents.length
visit(ast, 'heading', visitor);
visit(tree, 'heading', visitor)
function visitor(node) {
var initial = start(node);
var begin = initial.offset;
var index = begin - 1;
var character;
var diff;
var initial
var begin
var index
var character
var diff
if (generated(node)) {
return;
return
}
initial = start(node)
begin = initial.offset
index = begin - 1
while (++index < length) {
character = contents.charAt(index);
character = contents.charAt(index)
if (character !== ' ' && character !== '\t') {
break;
break
}
}
diff = index - begin;
diff = index - begin
if (diff) {
file.message(
'Remove ' + diff + ' ' + plural('space', diff) +
' before this heading',
'Remove ' + diff + ' ' + plural('space', diff) + ' before this heading',
{
line: initial.line,
column: initial.column + diff
}
);
)
}
}
}

View File

@ -23,29 +23,36 @@
* 1:1-1:16: This looks like a heading but has too many hashes
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-heading-like-paragraph', noHeadingLikeParagraph);
module.exports = rule(
'remark-lint:no-heading-like-paragraph',
noHeadingLikeParagraph
)
var fence = '#######';
var fence = '#######'
var reason = 'This looks like a heading but has too many hashes'
function noHeadingLikeParagraph(tree, file) {
visit(tree, 'paragraph', visitor);
visit(tree, 'paragraph', visitor)
function visitor(node) {
var head = node.children[0];
var head
if (
head &&
head.type === 'text' &&
head.value.slice(0, fence.length) === fence &&
!generated(node)
) {
file.message('This looks like a heading but has too many hashes', node);
if (!generated(node)) {
head = node.children[0]
if (
head &&
head.type === 'text' &&
head.value.slice(0, fence.length) === fence
) {
file.message(reason, node)
}
}
}
}

View File

@ -40,31 +40,37 @@
* 9:1-9:9: Dont add a trailing `;` to headings
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:no-heading-punctuation', noHeadingPunctuation);
module.exports = rule(
'remark-lint:no-heading-punctuation',
noHeadingPunctuation
)
function noHeadingPunctuation(ast, file, preferred) {
preferred = typeof preferred === 'string' ? preferred : '\\.,;:!?';
var defaults = '\\.,;:!?'
visit(ast, 'heading', visitor);
function noHeadingPunctuation(tree, file, pref) {
var expression = new RegExp(
'[' + (typeof pref === 'string' ? pref : defaults) + ']'
)
visit(tree, 'heading', visitor)
function visitor(node) {
var value = toString(node);
var value
if (generated(node)) {
return;
}
if (!generated(node)) {
value = toString(node)
value = value.charAt(value.length - 1)
value = value.charAt(value.length - 1);
if (new RegExp('[' + preferred + ']').test(value)) {
file.message('Dont add a trailing `' + value + '` to headings', node);
if (expression.test(value)) {
file.message('Dont add a trailing `' + value + '` to headings', node)
}
}
}
}

View File

@ -24,20 +24,22 @@
* 1:1-1:15: Do not use HTML in markdown
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-html', noHTML);
module.exports = rule('remark-lint:no-html', noHTML)
function noHTML(ast, file) {
visit(ast, 'html', visitor);
var reason = 'Do not use HTML in markdown'
function noHTML(tree, file) {
visit(tree, 'html', visitor)
function visitor(node) {
if (!generated(node) && !/^\s*<!--/.test(node.value)) {
file.message('Do not use HTML in markdown', node);
file.message(reason, node)
}
}
}

View File

@ -24,37 +24,29 @@
* 1:32-1:63: Dont pad `link` with inner spaces
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:no-inline-padding', noInlinePadding);
module.exports = rule('remark-lint:no-inline-padding', noInlinePadding)
function noInlinePadding(ast, file) {
visit(ast, visitor);
function noInlinePadding(tree, file) {
visit(tree, ['emphasis', 'strong', 'delete', 'image', 'link'], visitor)
function visitor(node) {
var type = node.type;
var contents;
var contents
if (generated(node)) {
return;
}
if (!generated(node)) {
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.message('Dont pad `' + type + '` with inner spaces', node);
if (
contents.charAt(0) === ' ' ||
contents.charAt(contents.length - 1) === ' '
) {
file.message('Dont pad `' + node.type + '` with inner spaces', node)
}
}
}

View File

@ -27,40 +27,35 @@
* 1:1-1:19: Dont use literal URLs without angle brackets
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var toString = require('mdast-util-to-string');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
var toString = require('mdast-util-to-string')
module.exports = rule('remark-lint:no-literal-urls', noLiteralURLs);
module.exports = rule('remark-lint:no-literal-urls', noLiteralURLs)
var start = position.start;
var end = position.end;
var MAILTO = 'mailto:';
var start = position.start
var end = position.end
var mailto = 'mailto:'
var reason = 'Dont use literal URLs without angle brackets'
function noLiteralURLs(ast, file) {
visit(ast, 'link', visitor);
function noLiteralURLs(tree, file) {
visit(tree, 'link', visitor)
function visitor(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 (generated(node)) {
return;
}
var children = node.children
var value = toString(node)
if (
initial === head &&
final === tail &&
(node.url === MAILTO + value || node.url === value)
!generated(node) &&
start(node).column === start(children[0]).column &&
end(node).column === end(children[children.length - 1]).column &&
(node.url === mailto + value || node.url === value)
) {
file.message('Dont use literal URLs without angle brackets', node);
file.message(reason, node)
}
}
}

View File

@ -61,52 +61,47 @@
* 2:1-2:7: Missing blank line before block node
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-missing-blank-lines', noMissingBlankLines);
module.exports = rule('remark-lint:no-missing-blank-lines', noMissingBlankLines)
function noMissingBlankLines(ast, file, options) {
var allow = (options || {}).exceptTightLists;
var reason = 'Missing blank line before block node'
visit(ast, visitor);
var types = [
'paragraph',
'blockquote',
'heading',
'code',
'yaml',
'html',
'list',
'table',
'thematicBreak'
]
function noMissingBlankLines(tree, file, pref) {
var allow = (pref || {}).exceptTightLists
visit(tree, visitor)
function visitor(node, index, parent) {
var next = parent && parent.children[index + 1];
var next
if (generated(node)) {
return;
}
if (!generated(node) && parent && (!allow || parent.type !== 'listItem')) {
next = parent.children[index + 1]
if (allow && parent && parent.type === 'listItem') {
return;
}
if (
next &&
applicable(node) &&
applicable(next) &&
position.start(next).line === position.end(node).line + 1
) {
file.message('Missing blank line before block node', next);
if (
next &&
types.indexOf(next.type) !== -1 &&
position.start(next).line === position.end(node).line + 1
) {
file.message(reason, next)
}
}
}
}
function applicable(node) {
return [
'paragraph',
'blockquote',
'heading',
'code',
'yaml',
'html',
'list',
'table',
'thematicBreak'
].indexOf(node.type) !== -1;
}

View File

@ -25,36 +25,43 @@
* 3:1-3:6: Dont use multiple top level headings (3:1)
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-multiple-toplevel-headings', noMultipleToplevelHeadings);
module.exports = rule(
'remark-lint:no-multiple-toplevel-headings',
noMultipleToplevelHeadings
)
function noMultipleToplevelHeadings(ast, file, preferred) {
var style = preferred ? preferred : 1;
var topLevelheading = false;
function noMultipleToplevelHeadings(tree, file, pref) {
var style = pref ? pref : 1
var topLevelheading = false
visit(ast, 'heading', visitor);
visit(tree, 'heading', visitor)
function visitor(node) {
var pos;
var pos
var reason
if (generated(node)) {
return;
}
if (node.depth === style) {
if (!generated(node) && node.depth === style) {
if (topLevelheading) {
pos = position.start(node);
pos = position.start(node)
file.message('Dont use multiple top level headings (' + pos.line + ':' + pos.column + ')', node);
reason =
'Dont use multiple top level headings (' +
pos.line +
':' +
pos.column +
')'
file.message(reason, node)
}
topLevelheading = node;
topLevelheading = true
}
}
}

View File

@ -74,75 +74,77 @@
* 23:2: Expected no indentation in paragraph content
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
module.exports = rule('remark-lint:no-paragraph-content-indent', noParagraphContentIndent);
module.exports = rule(
'remark-lint:no-paragraph-content-indent',
noParagraphContentIndent
)
var message = 'Expected no indentation in paragraph content';
var reason = 'Expected no indentation in paragraph content'
function noParagraphContentIndent(tree, file) {
visit(tree, 'paragraph', visitor);
visit(tree, 'paragraph', visitor)
function visitor(node) {
var first = true;
var first = true
visit(node, check);
visit(node, check)
return visit.SKIP
function check(node, pos, parent) {
var start = position.start(node);
var value;
var index;
var line;
var cumulative;
var indent;
var start = position.start(node)
var value
var index
var line
var cumulative
var indent
if (!applicable(node)) {
return;
return
}
if (!start.line || !node.position.indent) {
first = false;
return;
first = false
return
}
if (first && ws(toString(node).charAt(0))) {
file.message(message, node.position.start);
file.message(reason, node.position.start)
}
first = false;
value = toString(node);
index = value.indexOf('\n');
line = 0;
cumulative = 0;
indent = node.position.indent;
first = false
value = toString(node)
index = value.indexOf('\n')
line = 0
cumulative = 0
indent = node.position.indent
while (index !== -1) {
cumulative += indent[line];
cumulative += indent[line]
if (ws(value.charAt(index + 1))) {
file.message(
message,
{
line: start.line + line + 1,
column: indent[line],
offset: start.offset + index + cumulative
}
);
file.message(reason, {
line: start.line + line + 1,
column: indent[line],
offset: start.offset + index + cumulative
})
}
index = value.indexOf('\n', index + 1);
line++;
index = value.indexOf('\n', index + 1)
line++
}
if (value.charAt(value.length - 1) === '\n') {
node = head(parent.children[pos + 1]);
node = head(parent.children[pos + 1])
if (node && ws(toString(node).charAt(0))) {
file.message(message, node.position.start);
file.message(reason, node.position.start)
}
}
}
@ -150,27 +152,27 @@ function noParagraphContentIndent(tree, file) {
}
function ws(character) {
return character === ' ' || character === '\t';
return character === ' ' || character === '\t'
}
function head(node) {
while (node && 'children' in node) {
node = node.children[0];
node = node.children[0]
}
/* istanbul ignore if - shouldnt happen by default, could happen for void
* nodes though. */
if (!node || !applicable(node)) {
return null;
return null
}
return node;
return node
}
function applicable(node) {
return 'value' in node || node.alt;
return 'value' in node || node.alt
}
function toString(node) {
return 'value' in node ? node.value : node.alt;
return 'value' in node ? node.value : node.alt
}

View File

@ -23,39 +23,42 @@
* 1:1-1:17: Did you mean to use `[delta]` instead of `(delta)`, a reference?
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var generated = require('unist-util-generated');
var visit = require('unist-util-visit');
var rule = require('unified-lint-rule')
var generated = require('unist-util-generated')
var visit = require('unist-util-visit')
module.exports = rule('remark-lint:no-reference-like-url', noReferenceLikeURL);
module.exports = rule('remark-lint:no-reference-like-url', noReferenceLikeURL)
function noReferenceLikeURL(tree, file) {
var identifiers = [];
var identifiers = []
visit(tree, 'definition', find);
visit(tree, 'image', check);
visit(tree, 'link', check);
visit(tree, 'definition', find)
visit(tree, ['image', 'link'], check)
/* Find identifiers. */
function find(node) {
if (!generated(node)) {
identifiers.push(node.identifier.toLowerCase());
identifiers.push(node.identifier.toLowerCase())
}
}
/* Check `node`. */
function check(node) {
var url = node.url;
var url = node.url
var reason
if (identifiers.indexOf(url.toLowerCase()) !== -1) {
file.message(
'Did you mean to use `[' + url + ']` instead of ' +
'`(' + url + ')`, a reference?',
node
);
reason =
'Did you mean to use `[' +
url +
']` instead of ' +
'`(' +
url +
')`, a reference?'
file.message(reason, node)
}
}
}

View File

@ -21,6 +21,11 @@
* $ echo a > file
* ```
*
* Some empty code:
*
* ```command
* ```
*
* Its fine to use dollars in non-shell code.
*
* ```js
@ -39,13 +44,15 @@
* 1:1-4:4: Do not use dollar signs before shell-commands
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-shell-dollars', noShellDollars);
module.exports = rule('remark-lint:no-shell-dollars', noShellDollars)
var reason = 'Do not use dollar signs before shell-commands'
/* List of shell script file extensions (also used as code
* flags for syntax highlighting on GitHub):
@ -62,31 +69,36 @@ var flags = [
'tmux',
'tool',
'zsh'
];
]
function noShellDollars(ast, file) {
visit(ast, 'code', visitor);
function noShellDollars(tree, file) {
visit(tree, 'code', visitor)
function visitor(node) {
var language = node.lang;
var value = node.value;
var warn;
if (!language || generated(node)) {
return;
}
var lines
var line
var length
var index
/* Check both known shell-code and unknown code. */
if (flags.indexOf(language) !== -1) {
warn = value.length && value.split('\n').every(check);
if (!generated(node) && node.lang && flags.indexOf(node.lang) !== -1) {
lines = node.value.split('\n')
length = lines.length
index = -1
if (warn) {
file.message('Do not use dollar signs before shell-commands', node);
if (length <= 1) {
return
}
while (++index < length) {
line = lines[index]
if (line.trim() && !line.match(/^\s*\$\s*/)) {
return
}
}
file.message(reason, node)
}
}
function check(line) {
return Boolean(!line.trim() || line.match(/^\s*\$\s*/));
}
}

View File

@ -32,6 +32,11 @@ a
$ echo a > file
```
Some empty code:
```command
```
Its fine to use dollars in non-shell code.
```js

View File

@ -28,24 +28,25 @@
* 1:1-1:7: Use the trailing [] on reference images
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-shortcut-reference-image', noShortcutReferenceImage);
module.exports = rule(
'remark-lint:no-shortcut-reference-image',
noShortcutReferenceImage
)
function noShortcutReferenceImage(ast, file) {
visit(ast, 'imageReference', visitor);
var reason = 'Use the trailing [] on reference images'
function noShortcutReferenceImage(tree, file) {
visit(tree, 'imageReference', visitor)
function visitor(node) {
if (generated(node)) {
return;
}
if (node.referenceType === 'shortcut') {
file.message('Use the trailing [] on reference images', node);
if (!generated(node) && node.referenceType === 'shortcut') {
file.message(reason, node)
}
}
}

View File

@ -28,24 +28,25 @@
* 1:1-1:6: Use the trailing [] on reference links
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-shortcut-reference-link', noShortcutReferenceLink);
module.exports = rule(
'remark-lint:no-shortcut-reference-link',
noShortcutReferenceLink
)
function noShortcutReferenceLink(ast, file) {
visit(ast, 'linkReference', visitor);
var reason = 'Use the trailing [] on reference links'
function noShortcutReferenceLink(tree, file) {
visit(tree, 'linkReference', visitor)
function visitor(node) {
if (generated(node)) {
return;
}
if (node.referenceType === 'shortcut') {
file.message('Use the trailing [] on reference links', node);
if (!generated(node) && node.referenceType === 'shortcut') {
file.message(reason, node)
}
}
}

View File

@ -36,33 +36,38 @@
* 5:1-5:21: Do not indent table rows
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:no-table-indentation', noTableIndentation);
module.exports = rule('remark-lint:no-table-indentation', noTableIndentation)
function noTableIndentation(ast, file) {
visit(ast, 'table', visitor);
var reason = 'Do not indent table rows'
function noTableIndentation(tree, file) {
var contents = String(file)
visit(tree, 'table', visitor)
function visitor(node) {
var contents = file.toString();
if (generated(node)) {
return;
if (!generated(node)) {
node.children.forEach(each)
}
node.children.forEach(each);
return visit.SKIP
}
function each(row) {
var fence = contents.slice(position.start(row).offset, position.start(row.children[0]).offset);
function each(row) {
var fence = contents.slice(
position.start(row).offset,
position.start(row.children[0]).offset
)
if (fence.indexOf('|') > 1) {
file.message('Do not indent table rows', row);
}
if (fence.indexOf('|') > 1) {
file.message(reason, row)
}
}
}

View File

@ -50,22 +50,22 @@
* 13:41: Use spaces instead of hard-tabs
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var location = require('vfile-location');
var rule = require('unified-lint-rule')
var location = require('vfile-location')
module.exports = rule('remark-lint:no-tabs', noTabs);
module.exports = rule('remark-lint:no-tabs', noTabs)
function noTabs(ast, file) {
var content = file.toString();
var position = location(file).toPosition;
var index = -1;
var length = content.length;
var reason = 'Use spaces instead of hard-tabs'
while (++index < length) {
if (content.charAt(index) === '\t') {
file.message('Use spaces instead of hard-tabs', position(index));
}
function noTabs(tree, file) {
var content = String(file)
var position = location(file).toPosition
var index = content.indexOf('\t')
while (index !== -1) {
file.message(reason, position(index))
index = content.indexOf('\t', index + 1)
}
}

View File

@ -21,33 +21,34 @@
* 1:1-1:8: Found reference to undefined definition
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var generated = require('unist-util-generated');
var visit = require('unist-util-visit');
var rule = require('unified-lint-rule')
var generated = require('unist-util-generated')
var visit = require('unist-util-visit')
module.exports = rule('remark-lint:no-undefined-references', noUndefinedReferences);
module.exports = rule(
'remark-lint:no-undefined-references',
noUndefinedReferences
)
function noUndefinedReferences(ast, file) {
var map = {};
var reason = 'Found reference to undefined definition'
visit(ast, 'definition', mark);
visit(ast, 'footnoteDefinition', mark);
function noUndefinedReferences(tree, file) {
var map = {}
visit(ast, 'imageReference', find);
visit(ast, 'linkReference', find);
visit(ast, 'footnoteReference', find);
visit(tree, ['definition', 'footnoteDefinition'], mark)
visit(tree, ['imageReference', 'linkReference', 'footnoteReference'], find)
function mark(node) {
if (!generated(node)) {
map[node.identifier.toUpperCase()] = true;
map[node.identifier.toUpperCase()] = true
}
}
function find(node) {
if (!generated(node) && !map[node.identifier.toUpperCase()]) {
file.message('Found reference to undefined definition', node);
file.message(reason, node)
}
}
}

View File

@ -21,49 +21,43 @@
* 1:1-1:27: Found unused definition
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var generated = require('unist-util-generated');
var visit = require('unist-util-visit');
var rule = require('unified-lint-rule')
var generated = require('unist-util-generated')
var visit = require('unist-util-visit')
module.exports = rule('remark-lint:no-unused-definitions', noUnusedDefinitions);
module.exports = rule('remark-lint:no-unused-definitions', noUnusedDefinitions)
function noUnusedDefinitions(ast, file) {
var map = {};
var identifier;
var reason = 'Found unused definition'
visit(ast, 'definition', find);
visit(ast, 'footnoteDefinition', find);
function noUnusedDefinitions(tree, file) {
var map = {}
var identifier
var entry
visit(ast, 'imageReference', mark);
visit(ast, 'linkReference', mark);
visit(ast, 'footnoteReference', mark);
visit(tree, ['definition', 'footnoteDefinition'], find)
visit(tree, ['imageReference', 'linkReference', 'footnoteReference'], mark)
for (identifier in map) {
if (!map[identifier].used) {
file.message('Found unused definition', map[identifier].node);
entry = map[identifier]
if (!entry.used) {
file.message(reason, entry.node)
}
}
function find(node) {
if (generated(node)) {
return;
if (!generated(node)) {
map[node.identifier.toUpperCase()] = {node: node, used: false}
}
map[node.identifier.toUpperCase()] = {
node: node,
used: false
};
}
function mark(node) {
var info = map[node.identifier.toUpperCase()];
var info = map[node.identifier.toUpperCase()]
if (generated(node) || !info) {
return;
if (!generated(node) && info) {
info.used = true
}
info.used = true;
}
}

View File

@ -54,62 +54,64 @@
* 1:1: Invalid ordered list-item marker style `!`: use either `'.'` or `')'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:ordered-list-marker-style', orderedListMarkerStyle);
module.exports = rule(
'remark-lint:ordered-list-marker-style',
orderedListMarkerStyle
)
var start = position.start;
var start = position.start
var STYLES = {
var styles = {
')': true,
'.': true,
null: true
};
}
function orderedListMarkerStyle(ast, file, preferred) {
var contents = file.toString();
function orderedListMarkerStyle(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref !== 'string' || pref === 'consistent' ? null : pref
if (STYLES[preferred] !== true) {
file.fail('Invalid ordered list-item marker style `' + preferred + '`: use either `\'.\'` or `\')\'`');
if (styles[pref] !== true) {
file.fail(
'Invalid ordered list-item marker style `' +
pref +
"`: use either `'.'` or `')'`"
)
}
visit(ast, 'list', visitor);
visit(tree, 'list', visitor)
function visitor(node) {
var items = node.children;
var children = node.children
var length = node.ordered ? children.length : 0
var index = -1
var marker
var child
if (!node.ordered) {
return;
}
while (++index < length) {
child = children[index]
items.forEach(each);
if (!generated(child)) {
marker = contents
.slice(start(child).offset, start(child.children[0]).offset)
.replace(/\s|\d/g, '')
.replace(/\[[x ]?]\s*$/i, '')
function each(item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
if (generated(item)) {
return;
}
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.message('Marker style should be `' + preferred + '`', item);
if (pref) {
if (marker !== pref) {
file.message('Marker style should be `' + pref + '`', child)
}
} else {
pref = marker
}
}
}
}

View File

@ -89,6 +89,15 @@
*
* 2:1-2:8: Marker should be `1`, was `2`
*
* @example {"name": "also-invalid.md", "setting": "one", "label": "input"}
*
* 2. Foo
* 1. Bar
*
* @example {"name": "also-invalid.md", "setting": "one", "label": "output"}
*
* 1:1-1:8: Marker should be `1`, was `2`
*
* @example {"name": "invalid.md", "setting": "ordered", "label": "input"}
*
* 1. Foo
@ -103,74 +112,71 @@
* 1:1: Invalid ordered list-item marker value `invalid`: use either `'ordered'` or `'one'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:ordered-list-marker-value', orderedListMarkerValue);
module.exports = rule(
'remark-lint:ordered-list-marker-value',
orderedListMarkerValue
)
var start = position.start;
var start = position.start
var STYLES = {
ordered: true,
single: true,
one: true
};
var styles = {ordered: true, single: true, one: true}
function orderedListMarkerValue(ast, file, preferred) {
var contents = file.toString();
function orderedListMarkerValue(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred === 'string' ? preferred : 'ordered';
pref = typeof pref === 'string' ? pref : 'ordered'
if (STYLES[preferred] !== true) {
file.fail('Invalid ordered list-item marker value `' + preferred + '`: use either `\'ordered\'` or `\'one\'`');
if (styles[pref] !== true) {
file.fail(
'Invalid ordered list-item marker value `' +
pref +
"`: use either `'ordered'` or `'one'`"
)
}
visit(ast, 'list', visitor);
visit(tree, 'list', visitor)
function visitor(node) {
var items = node.children;
var shouldBe = (preferred === 'one' ? 1 : node.start) || 1;
var children = node.children
var shouldBe = (pref === 'one' ? 1 : node.start) || 1
var length = node.ordered ? children.length : 0
var index = -1
var child
var marker
/* Ignore unordered lists. */
if (!node.ordered) {
return;
}
while (++index < length) {
child = children[index]
items.forEach(each);
function each(item, index) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
/* Ignore first list item. */
if (index === 0) {
return;
/* Ignore generated nodes, first items. */
if (generated(child) || (index === 0 && pref !== 'one')) {
continue
}
/* Increase the expected line number when in
* `ordered` mode. */
if (preferred === 'ordered') {
shouldBe++;
if (pref === 'ordered') {
shouldBe++
}
/* Ignore generated nodes. */
if (generated(item)) {
return;
}
marker = contents.slice(initial, final).replace(/[\s.)]/g, '');
/* Support checkboxes. */
marker = Number(marker.replace(/\[[x ]?]\s*$/i, ''));
marker = Number(
contents
.slice(start(child).offset, start(child.children[0]).offset)
.replace(/[\s.)]/g, '')
.replace(/\[[x ]?]\s*$/i, '')
)
if (marker !== shouldBe) {
file.message('Marker should be `' + shouldBe + '`, was `' + marker + '`', item);
file.message(
'Marker should be `' + shouldBe + '`, was `' + marker + '`',
child
)
}
}
}

View File

@ -99,6 +99,23 @@ When configured with `'one'`.
2:1-2:8: Marker should be `1`, was `2`
```
##### `also-invalid.md`
When configured with `'one'`.
###### In
```markdown
2. Foo
1. Bar
```
###### Out
```text
1:1-1:8: Marker should be `1`, was `2`
```
##### `valid.md`
When configured with `'single'`.

View File

@ -56,50 +56,46 @@
* 1:1: Invalid preferred rule-style: provide a valid markdown rule, or `'consistent'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:rule-style', ruleStyle);
module.exports = rule('remark-lint:rule-style', ruleStyle)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function ruleStyle(ast, file, preferred) {
var contents = file.toString();
function ruleStyle(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (validateRuleStyle(preferred) !== true) {
file.fail('Invalid preferred rule-style: provide a valid markdown rule, or `\'consistent\'`');
if (pref !== null && /[^-_* ]/.test(pref)) {
file.fail(
"Invalid preferred rule-style: provide a valid markdown rule, or `'consistent'`"
)
}
visit(ast, 'thematicBreak', visitor);
visit(tree, 'thematicBreak', visitor)
function visitor(node) {
var initial = start(node).offset;
var final = end(node).offset;
var hr;
var initial = start(node).offset
var final = end(node).offset
var rule
if (generated(node)) {
return;
}
if (!generated(node)) {
rule = contents.slice(initial, final)
hr = contents.slice(initial, final);
if (preferred) {
if (hr !== preferred) {
file.message('Rules should use `' + preferred + '`', node);
if (pref) {
if (rule !== pref) {
file.message('Rules should use `' + pref + '`', node)
}
} else {
pref = rule
}
} else {
preferred = hr;
}
}
}
function validateRuleStyle(style) {
return style === null || !/[^-_* ]/.test(style);
}

View File

@ -52,43 +52,43 @@
* 1:1: Invalid strong marker `!`: use either `'consistent'`, `'*'`, or `'_'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:strong-marker', strongMarker);
module.exports = rule('remark-lint:strong-marker', strongMarker)
var MARKERS = {
'*': true,
_: true,
null: true
};
var markers = {'*': true, _: true, null: true}
function strongMarker(ast, file, preferred) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
function strongMarker(tree, file, pref) {
var contents = String(file)
if (MARKERS[preferred] !== true) {
file.fail('Invalid strong marker `' + preferred + '`: use either `\'consistent\'`, `\'*\'`, or `\'_\'`');
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (markers[pref] !== true) {
file.fail(
'Invalid strong marker `' +
pref +
"`: use either `'consistent'`, `'*'`, or `'_'`"
)
}
visit(ast, 'strong', visitor);
visit(tree, 'strong', visitor)
function visitor(node) {
var marker = file.toString().charAt(position.start(node).offset);
var marker = contents.charAt(position.start(node).offset)
if (generated(node)) {
return;
}
if (preferred) {
if (marker !== preferred) {
file.message('Strong should use `' + preferred + '` as a marker', node);
if (!generated(node)) {
if (pref) {
if (marker !== pref) {
file.message('Strong should use `' + pref + '` as a marker', node)
}
} else {
pref = marker
}
} else {
preferred = marker;
}
}
}

View File

@ -27,209 +27,264 @@
* | ----- | ----- |
* | Alpha | Bravo |
*
* @example {"name": "valid.md", "setting": "compact"}
*
* |A |B |
* |-----|-----|
* |Alpha|Bravo|
*
* @example {"name": "invalid.md", "label": "input"}
*
* | A | B |
* | -----| -----|
* | Alpha| Bravo|
*
* @example {"name": "invalid.md", "label": "output"}
*
* 3:5: Cell should be padded with 1 space, not 3
* 3:10: Cell should be padded
* 3:17: Cell should be padded
*
* @example {"name": "invalid.md", "label": "input", "setting": "padded"}
*
* | A | B |
* | :----|----: |
* | Alpha|Bravo |
*
* | C | D |
* | :----- | ---: |
* |Charlie | Delta|
*
* Too much padding isnt good either:
*
* | E | F | G | H |
* | :---- | -------- | :----: | -----: |
* | Echo | Foxtrot | Golf | Hotel |
*
* @example {"name": "invalid.md", "label": "output", "setting": "padded"}
*
* 3:8: Cell should be padded
* 3:9: Cell should be padded
* 7:2: Cell should be padded
* 7:17: Cell should be padded
* 13:23: Cell should be padded with 1 space, not 2
* 13:32: Cell should be padded with 1 space, not 2
*
* @example {"name": "valid.md", "setting": "compact"}
*
* |A |B |
* |-----|-----|
* |Alpha|Bravo|
*
* @example {"name": "invalid.md", "label": "input", "setting": "compact"}
*
* |A | B|
* |:----|-----:|
* |Alpha|Bravo |
* | A | B |
* | -----| -----|
* | Alpha| Bravo|
*
* |C | D|
* |:------|-----:|
* |Charlie|Delta |
*
* @example {"name": "invalid.md", "label": "output", "setting": "compact"}
*
* 3:13: Cell should be compact
* 3:5: Cell should be compact
* 3:12: Cell should be compact
* 7:15: Cell should be compact
*
* @example {"name": "valid-padded.md", "setting": "consistent"}
*
* | A | B |
* | ----- | ----- |
* | Alpha | Bravo |
*
* | C | D |
* | ------- | ----- |
* | Charlie | Delta |
*
* @example {"name": "invalid-padded.md", "label": "input", "setting": "consistent"}
*
* | A | B |
* | ----- | ----- |
* | Alpha | Bravo |
*
* | C | D |
* | :----- | ----: |
* |Charlie | Delta |
*
* @example {"name": "invalid-padded.md", "label": "output", "setting": "consistent"}
*
* 7:2: Cell should be padded
*
* @example {"name": "valid-compact.md", "setting": "consistent"}
*
* |A |B |
* |-----|-----|
* |Alpha|Bravo|
*
* |C |D |
* |-------|-----|
* |Charlie|Delta|
*
* @example {"name": "invalid-compact.md", "label": "input", "setting": "consistent"}
*
* |A |B |
* |-----|-----|
* |Alpha|Bravo|
*
* |C | D|
* |:------|-----:|
* |Charlie|Delta |
*
* @example {"name": "invalid-compact.md", "label": "output", "setting": "consistent"}
*
* 7:15: Cell should be compact
*
* @example {"name": "invalid.md", "label": "output", "setting": "invalid", "config": {"positionless": true}}
*
* 1:1: Invalid table-cell-padding style `invalid`
*
* @example {"name": "empty-heading.md"}
* @example {"name": "empty.md", "label": "input", "setting": "padded"}
*
* <!-- Empty heading cells are always OK. -->
* <!-- Empty cells are OK, but those surrounding them may not be. -->
*
* | | Alpha |
* | ----- | ------- |
* | Bravo | Charlie |
* | | Alpha | Bravo|
* | ------ | ----- | ---: |
* | Charlie| | Echo|
*
* @example {"name": "empty-body.md"}
* @example {"name": "empty.md", "label": "output", "setting": "padded"}
*
* <!-- Empty body cells are always OK. -->
* 3:25: Cell should be padded
* 5:10: Cell should be padded
* 5:25: Cell should be padded
*
* | Alpha | Bravo |
* | ------- | ------- |
* | Charlie | |
* @example {"name": "missing-body.md", "setting": "padded"}
*
* <!-- Missing cells are fine as well. -->
*
* | Alpha | Bravo | Charlie |
* | ----- | -------- | ------- |
* | Delta |
* | Echo | Foxtrott |
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:table-cell-padding', tableCellPadding);
module.exports = rule('remark-lint:table-cell-padding', tableCellPadding)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
var STYLES = {
null: true,
padded: true,
compact: true
};
var styles = {null: true, padded: true, compact: true}
function tableCellPadding(tree, file, preferred) {
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
function tableCellPadding(tree, file, pref) {
var contents = String(file)
if (STYLES[preferred] !== true) {
file.fail('Invalid table-cell-padding style `' + preferred + '`');
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (styles[pref] !== true) {
file.fail('Invalid table-cell-padding style `' + pref + '`')
}
visit(tree, 'table', visitor);
visit(tree, 'table', visitor)
function visitor(node) {
var rows = node.children;
var contents = String(file);
var starts = [];
var ends = [];
var cells = [];
var style;
var sizes;
var rows = node.children
var sizes = new Array(node.align.length)
var length = generated(node) ? -1 : rows.length
var index = -1
var entries = []
var style
var row
var cells
var column
var cellCount
var cell
var next
var fence
var pos
var entry
var final
if (generated(node)) {
return;
}
/* Check rows. */
while (++index < length) {
row = rows[index]
cells = row.children
cellCount = cells.length
column = -2 /* Start without a first cell */
next = null
final = undefined
rows.forEach(eachRow);
/* Check fences (before, between, and after cells) */
while (++column < cellCount) {
cell = next
next = cells[column + 1]
sizes = inferSizes(node);
fence = contents.slice(
cell ? end(cell).offset : start(row).offset,
next ? start(next).offset : end(row).offset
)
if (preferred === 'padded') {
style = 1;
} else if (preferred === 'compact') {
style = 0;
} else {
style = null;
starts.concat(ends).some(inferStyle);
}
pos = fence.indexOf('|')
cells.forEach(checkCell);
if (cell && cell.children.length !== 0 && final !== undefined) {
entries.push({node: cell, start: final, end: pos, index: column})
function eachRow(row) {
var children = row.children;
/* Detect max space per column. */
sizes[column] = Math.max(sizes[column] || 0, size(cell))
} else {
final = undefined
}
check(start(row).offset, start(children[0]).offset, null, children[0]);
ends.pop(); /* Ignore end before row. */
children.forEach(eachCell);
starts.pop(); /* Ignore start after row */
function eachCell(cell, index) {
var next = children[index + 1] || null;
check(end(cell).offset, start(next).offset || end(row).offset, cell, next);
cells.push(cell);
}
}
function inferStyle(pos) {
if (pos === undefined) {
return false;
}
style = Math.min(pos, 1);
return true;
}
function check(initial, final, prev, next) {
var fence = contents.slice(initial, final);
var pos = fence.indexOf('|');
ends.push(prev && pos !== -1 && prev.children.length !== 0 ? pos : undefined);
starts.push(next && next.children.length !== 0 ? fence.length - pos - 1 : undefined);
}
function checkCell(cell, index) {
/* Ignore, when compact, every cell except the biggest in the column. */
if (style === 0 && size(cell) < sizes[index % sizes.length]) {
return;
}
checkSide('start', cell, starts[index], index);
checkSide('end', cell, ends[index]);
}
function checkSide(side, cell, spacing, index) {
var message;
if (spacing === undefined || spacing === style) {
return;
}
message = 'Cell should be ';
if (style === 0) {
message += 'compact';
} else {
message += 'padded';
if (spacing > style) {
message += ' with 1 space, not ' + spacing;
/* May be right or center aligned. */
if (size(cell) < sizes[index % sizes.length]) {
return;
}
if (next && next.children.length !== 0) {
final = fence.length - pos - 1
} else {
final = undefined
}
}
file.message(message, cell.position[side]);
}
}
}
function inferSizes(tree) {
var sizes = new Array(tree.align.length);
if (pref) {
style = pref === 'padded' ? 1 : 0
} else {
style = entries[0] && (!entries[0].start || !entries[0].end) ? 0 : 1
}
tree.children.forEach(row);
index = -1
length = entries.length
return sizes;
while (++index < length) {
entry = entries[index]
checkSide('start', entry, style, sizes)
checkSide('end', entry, style, sizes)
}
function row(node) {
node.children.forEach(cell);
return visit.SKIP
}
function cell(node, index) {
sizes[index] = Math.max(sizes[index] || 0, size(node));
function checkSide(side, entry, style, sizes) {
var cell = entry.node
var spacing = entry[side]
var index = entry.index
var reason
if (spacing === undefined || spacing === style) {
return
}
reason = 'Cell should be '
if (style === 0) {
reason += 'compact'
/* Ignore every cell except the biggest in the column. */
if (size(cell) < sizes[index]) {
return
}
} else {
reason += 'padded'
if (spacing > style) {
reason += ' with 1 space, not ' + spacing
/* May be right or center aligned. */
if (size(cell) < sizes[index]) {
return
}
}
}
file.message(reason, cell.position[side])
}
}
function size(node) {
return end(node).offset - start(node).offset;
return end(node).offset - start(node).offset
}

View File

@ -56,6 +56,16 @@ When configured with `'padded'`.
| A | B |
| :----|----: |
| Alpha|Bravo |
| C | D |
| :----- | ---: |
|Charlie | Delta|
Too much padding isnt good either:
| E | F | G | H |
| :---- | -------- | :----: | -----: |
| Echo | Foxtrot | Golf | Hotel |
```
###### Out
@ -63,8 +73,53 @@ When configured with `'padded'`.
```text
3:8: Cell should be padded
3:9: Cell should be padded
7:2: Cell should be padded
7:17: Cell should be padded
13:23: Cell should be padded with 1 space, not 2
13:32: Cell should be padded with 1 space, not 2
```
##### `empty.md`
When configured with `'padded'`.
###### In
```markdown
<!-- Empty cells are OK, but those surrounding them may not be. -->
| | Alpha | Bravo|
| ------ | ----- | ---: |
| Charlie| | Echo|
```
###### Out
```text
3:25: Cell should be padded
5:10: Cell should be padded
5:25: Cell should be padded
```
##### `missing-body.md`
When configured with `'padded'`.
###### In
```markdown
<!-- Missing cells are fine as well. -->
| Alpha | Bravo | Charlie |
| ----- | -------- | ------- |
| Delta |
| Echo | Foxtrott |
```
###### Out
No messages.
##### `valid.md`
When configured with `'compact'`.
@ -87,68 +142,108 @@ When configured with `'compact'`.
###### In
```markdown
|A | B|
|:----|-----:|
|Alpha|Bravo |
```
###### Out
```text
3:13: Cell should be compact
```
##### `invalid.md`
###### In
```markdown
| A | B |
| -----| -----|
| Alpha| Bravo|
|C | D|
|:------|-----:|
|Charlie|Delta |
```
###### Out
```text
3:5: Cell should be padded with 1 space, not 3
3:10: Cell should be padded
3:17: Cell should be padded
3:5: Cell should be compact
3:12: Cell should be compact
7:15: Cell should be compact
```
##### `empty-heading.md`
##### `valid-padded.md`
When configured with `'consistent'`.
###### In
```markdown
<!-- Empty heading cells are always OK. -->
| A | B |
| ----- | ----- |
| Alpha | Bravo |
| | Alpha |
| ----- | ------- |
| Bravo | Charlie |
| C | D |
| ------- | ----- |
| Charlie | Delta |
```
###### Out
No messages.
##### `empty-body.md`
##### `invalid-padded.md`
When configured with `'consistent'`.
###### In
```markdown
<!-- Empty body cells are always OK. -->
| A | B |
| ----- | ----- |
| Alpha | Bravo |
| Alpha | Bravo |
| ------- | ------- |
| Charlie | |
| C | D |
| :----- | ----: |
|Charlie | Delta |
```
###### Out
```text
7:2: Cell should be padded
```
##### `valid-compact.md`
When configured with `'consistent'`.
###### In
```markdown
|A |B |
|-----|-----|
|Alpha|Bravo|
|C |D |
|-------|-----|
|Charlie|Delta|
```
###### Out
No messages.
##### `invalid-compact.md`
When configured with `'consistent'`.
###### In
```markdown
|A |B |
|-----|-----|
|Alpha|Bravo|
|C | D|
|:------|-----:|
|Charlie|Delta |
```
###### Out
```text
7:15: Cell should be compact
```
##### `invalid.md`
When configured with `'invalid'`.

View File

@ -41,62 +41,69 @@
* 3:17-3:18: Misaligned table fence
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:table-pipe-alignment', tablePipeAlignment);
module.exports = rule('remark-lint:table-pipe-alignment', tablePipeAlignment)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function tablePipeAlignment(ast, file) {
visit(ast, 'table', visitor);
var reason = 'Misaligned table fence'
function tablePipeAlignment(tree, file) {
var contents = String(file)
visit(tree, 'table', visitor)
function visitor(node) {
var contents = file.toString();
var indices = [];
var offset;
var line;
var rows = node.children
var length = generated(node) ? 0 : rows.length
var index = -1
var indices = []
var row
var cells
var begin
var column
var columns
var cell
var initial
var final
var next
var nextIndex
var fence
var pos
if (generated(node)) {
return;
}
while (++index < length) {
row = rows[index]
begin = start(row)
cells = row.children
columns = cells.length
column = -2 /* Start without a first cell */
next = null
node.children.forEach(visitRow);
while (++column < columns) {
cell = next
nextIndex = column + 1
next = cells[nextIndex]
function visitRow(row) {
var cells = row.children;
initial = cell ? end(cell).offset : start(row).offset
final = next ? start(next).offset : end(row).offset
fence = contents.slice(initial, final)
pos = initial + fence.indexOf('|') - begin.offset + 1
line = start(row).line;
offset = start(row).offset;
check(start(row).offset, start(cells[0]).offset, 0);
row.children.forEach(visitCell);
function visitCell(cell, index) {
var next = start(cells[index + 1]).offset || end(row).offset;
check(end(cell).offset, next, index + 1);
}
}
/* Check that all pipes after each column are at
* aligned. */
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.message('Misaligned table fence', {
start: {line: line, column: pos},
end: {line: line, column: pos + 1}
});
if (indices[nextIndex] === undefined || indices[nextIndex] === null) {
indices[nextIndex] = pos
} else if (pos !== indices[nextIndex]) {
file.message(reason, {
start: {line: begin.line, column: pos},
end: {line: begin.line, column: pos + 1}
})
}
}
}
}

View File

@ -36,43 +36,54 @@
* 3:14: Missing final pipe in table fence
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:table-pipes', tablePipes);
module.exports = rule('remark-lint:table-pipes', tablePipes)
var start = position.start;
var end = position.end;
var start = position.start
var end = position.end
function tablePipes(ast, file) {
visit(ast, 'table', visitor);
var reasonStart = 'Missing initial pipe in table fence'
var reasonEnd = 'Missing final pipe in table fence'
function tablePipes(tree, file) {
var contents = String(file)
visit(tree, 'table', visitor)
function visitor(node) {
var contents = file.toString();
var rows = node.children
var length = rows.length
var index = -1
var row
var cells
var head
var tail
var initial
var final
node.children.forEach(visitRow);
while (++index < length) {
row = rows[index]
function visitRow(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 (!generated(row)) {
cells = row.children
head = cells[0]
tail = cells[cells.length - 1]
initial = contents.slice(start(row).offset, start(head).offset)
final = contents.slice(end(tail).offset, end(row).offset)
if (generated(row)) {
return;
}
if (initial.indexOf('|') === -1) {
file.message(reasonStart, start(row))
}
if (initial.indexOf('|') === -1) {
file.message('Missing initial pipe in table fence', start(row));
}
if (final.indexOf('|') === -1) {
file.message('Missing final pipe in table fence', end(row));
if (final.indexOf('|') === -1) {
file.message(reasonEnd, end(row))
}
}
}
}

View File

@ -65,63 +65,65 @@
* 1:1: Invalid unordered list-item marker style `!`: use either `'-'`, `'*'`, or `'+'`
*/
'use strict';
'use strict'
var rule = require('unified-lint-rule');
var visit = require('unist-util-visit');
var position = require('unist-util-position');
var generated = require('unist-util-generated');
var rule = require('unified-lint-rule')
var visit = require('unist-util-visit')
var position = require('unist-util-position')
var generated = require('unist-util-generated')
module.exports = rule('remark-lint:unordered-list-marker-style', unorderedListMarkerStyle);
module.exports = rule(
'remark-lint:unordered-list-marker-style',
unorderedListMarkerStyle
)
var start = position.start;
var start = position.start
var STYLES = {
var styles = {
'-': true,
'*': true,
'+': true,
null: true
};
}
function unorderedListMarkerStyle(ast, file, preferred) {
var contents = file.toString();
function unorderedListMarkerStyle(tree, file, pref) {
var contents = String(file)
preferred = typeof preferred !== 'string' || preferred === 'consistent' ? null : preferred;
pref = typeof pref === 'string' && pref !== 'consistent' ? pref : null
if (STYLES[preferred] !== true) {
file.fail('Invalid unordered list-item marker style `' + preferred + '`: use either `\'-\'`, `\'*\'`, or `\'+\'`');
if (styles[pref] !== true) {
file.fail(
'Invalid unordered list-item marker style `' +
pref +
"`: use either `'-'`, `'*'`, or `'+'`"
)
}
visit(ast, 'list', visitor);
visit(tree, 'list', visitor)
function visitor(node) {
var items = node.children;
var children = node.children
var length = node.ordered ? 0 : children.length
var index = -1
var child
var marker
if (node.ordered) {
return;
}
while (++index < length) {
child = children[index]
items.forEach(visitItem);
if (!generated(child)) {
marker = contents
.slice(start(child).offset, start(child.children[0]).offset)
.replace(/\[[x ]?]\s*$/i, '')
.replace(/\s/g, '')
function visitItem(item) {
var head = item.children[0];
var initial = start(item).offset;
var final = start(head).offset;
var marker;
if (generated(item)) {
return;
}
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.message('Marker style should be `' + preferred + '`', item);
if (pref) {
if (marker !== pref) {
file.message('Marker style should be `' + pref + '`', child)
}
} else {
pref = marker
}
}
}
}

View File

@ -1,16 +1,16 @@
'use strict';
'use strict'
var control = require('remark-message-control');
var control = require('remark-message-control')
module.exports = lint;
module.exports = lint
/* `remark-lint`. This adds support for ignoring stuff from
* messages (`<!--lint ignore-->`).
* All rules are in their own packages and presets. */
function lint() {
this.use(lintMessageControl);
this.use(lintMessageControl)
}
function lintMessageControl() {
return control({name: 'lint', source: 'remark-lint'});
return control({name: 'lint', source: 'remark-lint'})
}

View File

@ -4,7 +4,7 @@
* settings that enforce consistency.
*/
'use strict';
'use strict'
module.exports.plugins = [
require('remark-lint'),
@ -20,4 +20,4 @@ module.exports.plugins = [
[require('remark-lint-rule-style'), 'consistent'],
[require('remark-lint-strong-marker'), 'consistent'],
[require('remark-lint-table-cell-padding'), 'consistent']
];
]

View File

@ -90,7 +90,7 @@
* ```
*/
'use strict';
'use strict'
module.exports.plugins = [
require('remark-lint'),
@ -232,4 +232,4 @@ module.exports.plugins = [
/* http://www.cirosantilli.com/markdown-style-guide/#email-automatic-links.
* Not checked. */
];
]

View File

@ -5,7 +5,7 @@
* across vendors.
*/
'use strict';
'use strict'
module.exports.plugins = [
require('remark-lint'),
@ -29,4 +29,4 @@ module.exports.plugins = [
require('remark-lint-no-shortcut-reference-link'),
require('remark-lint-no-undefined-references'),
require('remark-lint-no-unused-definitions')
];
]

View File

@ -1,60 +1,60 @@
'use strict';
'use strict'
var wrapped = require('wrapped');
var wrapped = require('wrapped')
module.exports = factory;
module.exports = factory
function factory(id, rule) {
var parts = id.split(':');
var source = parts[0];
var ruleId = parts[1];
var fn = wrapped(rule);
var parts = id.split(':')
var source = parts[0]
var ruleId = parts[1]
var fn = wrapped(rule)
/* istanbul ignore if - possibly useful if externalised later. */
if (!ruleId) {
ruleId = source;
source = null;
ruleId = source
source = null
}
attacher.displayName = id;
attacher.displayName = id
return attacher;
return attacher
function attacher(raw) {
var config = coerce(ruleId, raw);
var severity = config[0];
var options = config[1];
var fatal = severity === 2;
var config = coerce(ruleId, raw)
var severity = config[0]
var options = config[1]
var fatal = severity === 2
return severity ? transformer : undefined;
return severity ? transformer : undefined
function transformer(tree, file, next) {
var index = file.messages.length;
var index = file.messages.length
fn(tree, file, options, done);
fn(tree, file, options, done)
function done(err) {
var messages = file.messages;
var message;
var messages = file.messages
var message
/* Add the error, if not already properly added. */
/* istanbul ignore if - only happens for incorrect plugins */
if (err && messages.indexOf(err) === -1) {
try {
file.fail(err);
file.fail(err)
} catch (err) {}
}
while (index < messages.length) {
message = messages[index];
message.ruleId = ruleId;
message.source = source;
message.fatal = fatal;
message = messages[index]
message.ruleId = ruleId
message.source = source
message.fatal = fatal
index++;
index++
}
next();
next()
}
}
}
@ -62,53 +62,55 @@ function factory(id, rule) {
/* Coerce a value to a severity--options tuple. */
function coerce(name, value) {
var def = 1;
var result;
var level;
var def = 1
var result
var level
/* istanbul ignore if - Handled by unified in v6.0.0 */
if (typeof value === 'boolean') {
result = [value];
result = [value]
} else if (value == null) {
result = [def];
result = [def]
} else if (
typeof value === 'object' &&
(
typeof value[0] === 'number' ||
(typeof value[0] === 'number' ||
typeof value[0] === 'boolean' ||
typeof value[0] === 'string'
)
typeof value[0] === 'string')
) {
result = value.concat();
result = value.concat()
} else {
result = [1, value];
result = [1, value]
}
level = result[0];
level = result[0]
if (typeof level === 'boolean') {
level = level ? 1 : 0;
level = level ? 1 : 0
} else if (typeof level === 'string') {
if (level === 'off') {
level = 0;
level = 0
} else if (level === 'on' || level === 'warn') {
level = 1;
level = 1
} else if (level === 'error') {
level = 2;
level = 2
} else {
level = 1;
result = [level, result];
level = 1
result = [level, result]
}
}
if (level < 0 || level > 2) {
throw new Error(
'Invalid severity `' + level + '` for `' + name + '`, ' +
'expected 0, 1, or 2'
);
'Invalid severity `' +
level +
'` for `' +
name +
'`, ' +
'expected 0, 1, or 2'
)
}
result[0] = level;
result[0] = level
return result;
return result
}

View File

@ -1,62 +1,74 @@
'use strict';
'use strict'
var fs = require('fs');
var path = require('path');
var u = require('unist-builder');
var dox = require('dox');
var chalk = require('chalk');
var remark = require('remark');
var strip = require('strip-indent');
var trim = require('trim');
var parseAuthor = require('parse-author');
var remote = require('../package.json').repository;
var find = require('./util/find');
var presets = require('./util/presets');
var fs = require('fs')
var path = require('path')
var u = require('unist-builder')
var dox = require('dox')
var chalk = require('chalk')
var remark = require('remark')
var strip = require('strip-indent')
var trim = require('trim')
var parseAuthor = require('parse-author')
var remote = require('../package.json').repository
var find = require('./util/find')
var presets = require('./util/presets')
var root = path.join(process.cwd(), 'packages');
var root = path.join(process.cwd(), 'packages')
presets(root).forEach(function (basename) {
var base = path.resolve(root, basename);
var pack = require(path.join(base, 'package.json'));
var doc = fs.readFileSync(path.join(base, 'index.js'), 'utf8');
var tags = dox.parseComments(doc)[0].tags;
var author = parseAuthor(pack.author);
var description = trim(strip(find(tags, 'fileoverview')));
var rows = [];
var children;
var short = basename.replace(/^remark-/, '');
presets(root).forEach(function(basename) {
var base = path.resolve(root, basename)
var pack = require(path.join(base, 'package.json'))
var doc = fs.readFileSync(path.join(base, 'index.js'), 'utf8')
var tags = dox.parseComments(doc)[0].tags
var author = parseAuthor(pack.author)
var description = trim(strip(find(tags, 'fileoverview')))
var rows = []
var children
var short = basename.replace(/^remark-/, '')
if (basename !== pack.name) {
throw new Error(
'Expected package name (`' + pack.name + '`) to be the same as ' +
'directory name (`' + basename + '`)'
);
'Expected package name (`' +
pack.name +
'`) to be the same as ' +
'directory name (`' +
basename +
'`)'
)
}
rows.push(u('tableRow', [
u('tableCell', [u('text', 'Rule')]),
u('tableCell', [u('text', 'Setting')])
]));
rows.push(
u('tableRow', [
u('tableCell', [u('text', 'Rule')]),
u('tableCell', [u('text', 'Setting')])
])
)
doc.replace(/require\('remark-lint-([^']+)'\)(?:, ([^\]]+)])?/g, function ($0, rule, option) {
var url = remote + '/tree/master/packages/remark-lint-' + rule;
doc.replace(/require\('remark-lint-([^']+)'\)(?:, ([^\]]+)])?/g, function(
$0,
rule,
option
) {
var url = remote + '/tree/master/packages/remark-lint-' + rule
rows.push(u('tableRow', [
u('tableCell', [
u('link', {url: url, title: null}, [u('inlineCode', rule)])
]),
u('tableCell', option ? [u('inlineCode', option)] : [])
]));
rows.push(
u('tableRow', [
u('tableCell', [
u('link', {url: url, title: null}, [u('inlineCode', rule)])
]),
u('tableCell', option ? [u('inlineCode', option)] : [])
])
)
return '';
});
return ''
})
children = [
u('html', '<!--This file is generated-->'),
u('heading', {depth: 1}, [u('text', basename)])
];
]
children = children.concat(remark().parse(description).children);
children = children.concat(remark().parse(description).children)
children.push(
u('heading', {depth: 2}, [u('text', 'Rules')]),
@ -70,39 +82,51 @@ presets(root).forEach(function (basename) {
u('paragraph', [u('text', 'npm:')]),
u('code', {lang: 'sh'}, 'npm install ' + basename),
u('heading', {depth: 2}, [u('text', 'Usage')]),
u('paragraph', [u('text', 'You probably want to use it on the CLI through a config file:')]),
u('code', {lang: 'diff'}, [
' ...',
' "remarkConfig": {',
'+ "plugins": ["' + short + '"]',
' }',
' ...'
].join('\n')),
u('paragraph', [
u('text', 'You probably want to use it on the CLI through a config file:')
]),
u(
'code',
{lang: 'diff'},
[
' ...',
' "remarkConfig": {',
'+ "plugins": ["' + short + '"]',
' }',
' ...'
].join('\n')
),
u('paragraph', [u('text', 'Or use it on the CLI directly')]),
u('code', {lang: 'sh'}, 'remark -u ' + short + ' readme.md'),
u('paragraph', [u('text', 'Or use this on the API:')]),
u('code', {lang: 'diff'}, [
' var remark = require(\'remark\');',
' var report = require(\'vfile-reporter\');',
'',
' remark()',
'+ .use(require(\'' + basename + '\'))',
' .process(\'_Emphasis_ and **importance**\', function (err, file) {',
' console.error(report(err || file));',
' });'
].join('\n')),
u(
'code',
{lang: 'diff'},
[
" var remark = require('remark');",
" var report = require('vfile-reporter');",
'',
' remark()',
"+ .use(require('" + basename + "'))",
" .process('_Emphasis_ and **importance**', function (err, file) {",
' console.error(report(err || file));',
' });'
].join('\n')
),
u('heading', {depth: 2}, [u('text', 'License')]),
u('paragraph', [
u('link', {url: remote + '/blob/master/LICENSE'}, [u('text', pack.license)]),
u('link', {url: remote + '/blob/master/LICENSE'}, [
u('text', pack.license)
]),
u('text', ' © '),
u('link', {url: author.url}, [u('text', author.name)])
])
);
)
fs.writeFileSync(
path.join(base, 'readme.md'),
remark().stringify(u('root', children))
);
)
console.log(chalk.green('✓') + ' wrote `readme.md` in `' + basename + '`');
});
console.log(chalk.green('✓') + ' wrote `readme.md` in `' + basename + '`')
})

View File

@ -1,100 +1,120 @@
'use strict';
'use strict'
var fs = require('fs');
var path = require('path');
var inspect = require('util').inspect;
var u = require('unist-builder');
var chalk = require('chalk');
var remark = require('remark');
var parseAuthor = require('parse-author');
var remote = require('../package.json').repository;
var rules = require('./util/rules');
var rule = require('./util/rule');
var presets = require('./util/presets');
var chars = require('./characters');
var fs = require('fs')
var path = require('path')
var inspect = require('util').inspect
var u = require('unist-builder')
var chalk = require('chalk')
var remark = require('remark')
var parseAuthor = require('parse-author')
var remote = require('../package.json').repository
var rules = require('./util/rules')
var rule = require('./util/rule')
var presets = require('./util/presets')
var chars = require('./characters')
var root = path.join(process.cwd(), 'packages');
var root = path.join(process.cwd(), 'packages')
presets = presets(root).map(function (name) {
var doc = fs.readFileSync(path.join(root, name, 'index.js'), 'utf8');
var packages = {};
presets = presets(root).map(function(name) {
var doc = fs.readFileSync(path.join(root, name, 'index.js'), 'utf8')
var packages = {}
doc.replace(/require\('(remark-lint-[^']+)'\)(?:, ([^\]]+)])?/g, function ($0, rule, option) {
packages[rule] = option || null;
return '';
});
doc.replace(/require\('(remark-lint-[^']+)'\)(?:, ([^\]]+)])?/g, function(
$0,
rule,
option
) {
packages[rule] = option || null
return ''
})
return {
name: name,
packages: packages
};
});
}
})
rules(root).forEach(function (basename) {
var base = path.resolve(root, basename);
var pack = require(path.join(base, 'package.json'));
var info = rule(base);
var tests = info.tests;
var author = parseAuthor(pack.author);
var short = basename.replace(/^remark-/, '');
var includes;
rules(root).forEach(function(basename) {
var base = path.resolve(root, basename)
var pack = require(path.join(base, 'package.json'))
var info = rule(base)
var tests = info.tests
var author = parseAuthor(pack.author)
var short = basename.replace(/^remark-/, '')
var includes
var children = [
u('html', '<!--This file is generated-->'),
u('heading', {depth: 1}, [u('text', basename)])
].concat(remark().parse(info.description).children);
].concat(remark().parse(info.description).children)
if (basename !== pack.name) {
throw new Error(
'Expected package name (`' + pack.name + '`) to be the same as ' +
'directory name (`' + basename + '`)'
);
'Expected package name (`' +
pack.name +
'`) to be the same as ' +
'directory name (`' +
basename +
'`)'
)
}
includes = presets.filter(function (preset) {
return basename in preset.packages;
});
includes = presets.filter(function(preset) {
return basename in preset.packages
})
children.push(u('heading', {depth: 2}, [u('text', 'Presets')]));
children.push(u('heading', {depth: 2}, [u('text', 'Presets')]))
if (includes.length === 0) {
children.push(u('paragraph', [
u('text', 'This rule is not included in any default preset')
]));
children.push(
u('paragraph', [
u('text', 'This rule is not included in any default preset')
])
)
} else {
children.push(
u('paragraph', [u('text', 'This rule is included in the following presets:')]),
u('table', {align: []}, [
u('tableRow', [
u('tableCell', [u('text', 'Preset')]),
u('tableCell', [u('text', 'Setting')])
])
].concat(includes.map(function (preset) {
var url = remote + '/tree/master/packages/' + preset.name;
var option = preset.packages[pack.name];
u('paragraph', [
u('text', 'This rule is included in the following presets:')
]),
u(
'table',
{align: []},
[
u('tableRow', [
u('tableCell', [u('text', 'Preset')]),
u('tableCell', [u('text', 'Setting')])
])
].concat(
includes.map(function(preset) {
var url = remote + '/tree/master/packages/' + preset.name
var option = preset.packages[pack.name]
return u('tableRow', [
u('tableCell', [
u('link', {url: url, title: null}, [u('inlineCode', preset.name)])
]),
u('tableCell', option ? [u('inlineCode', option)] : [])
]);
})))
);
return u('tableRow', [
u('tableCell', [
u('link', {url: url, title: null}, [
u('inlineCode', preset.name)
])
]),
u('tableCell', option ? [u('inlineCode', option)] : [])
])
})
)
)
)
}
Object.keys(tests).forEach(function (setting, index) {
var fixtures = tests[setting];
Object.keys(tests).forEach(function(setting, index) {
var fixtures = tests[setting]
if (index === 0) {
children.push(u('heading', {depth: 2}, [u('text', 'Example')]));
children.push(u('heading', {depth: 2}, [u('text', 'Example')]))
}
Object.keys(fixtures).forEach(function (fileName) {
var fixture = fixtures[fileName];
var label = inspect(JSON.parse(setting));
var clean = fixture.input;
Object.keys(fixtures).forEach(function(fileName) {
var fixture = fixtures[fileName]
var label = inspect(JSON.parse(setting))
var clean = fixture.input
children.push(u('heading', {depth: 5}, [u('inlineCode', fileName)]));
children.push(u('heading', {depth: 5}, [u('inlineCode', fileName)]))
if (label !== 'true') {
children.push(
@ -103,84 +123,96 @@ rules(root).forEach(function (basename) {
u('inlineCode', label),
u('text', '.')
])
);
)
}
if (fixture.input != null && fixture.input.trim() !== '') {
children.push(u('heading', {depth: 6}, [u('text', 'In')]));
children.push(u('heading', {depth: 6}, [u('text', 'In')]))
chars.forEach(function (char) {
var next = clean.replace(char.in, char.out);
chars.forEach(function(char) {
var next = clean.replace(char.in, char.out)
if (clean !== next) {
children.push(u('paragraph', [
u('text', 'Note: '),
u('inlineCode', char.char),
u('text', ' represents ' + char.name + '.')
]));
children.push(
u('paragraph', [
u('text', 'Note: '),
u('inlineCode', char.char),
u('text', ' represents ' + char.name + '.')
])
)
clean = next;
clean = next
}
});
})
children.push(u('code', {lang: 'markdown'}, fixture.input));
children.push(u('code', {lang: 'markdown'}, fixture.input))
}
children.push(u('heading', {depth: 6}, [u('text', 'Out')]));
children.push(u('heading', {depth: 6}, [u('text', 'Out')]))
if (fixture.output.length === 0) {
children.push(u('paragraph', [u('text', 'No messages.')]));
children.push(u('paragraph', [u('text', 'No messages.')]))
} else {
children.push(u('code', {lang: 'text'}, fixture.output.join('\n')));
children.push(u('code', {lang: 'text'}, fixture.output.join('\n')))
}
});
});
})
})
children = children.concat([
u('heading', {depth: 2}, [u('text', 'Install')]),
u('code', {lang: 'sh'}, 'npm install ' + basename),
u('heading', {depth: 2}, [u('text', 'Usage')]),
u('paragraph', [u('text', 'You probably want to use it on the CLI through a config file:')]),
u('code', {lang: 'diff'}, [
' ...',
' "remarkConfig": {',
' "plugins": [',
' ...',
' "lint",',
'+ "' + short + '",',
' ...',
' ]',
' }',
' ...'
].join('\n')),
u('paragraph', [
u('text', 'You probably want to use it on the CLI through a config file:')
]),
u(
'code',
{lang: 'diff'},
[
' ...',
' "remarkConfig": {',
' "plugins": [',
' ...',
' "lint",',
'+ "' + short + '",',
' ...',
' ]',
' }',
' ...'
].join('\n')
),
u('paragraph', [u('text', 'Or use it on the CLI directly')]),
u('code', {lang: 'sh'}, 'remark -u lint -u ' + short + ' readme.md'),
u('paragraph', [u('text', 'Or use this on the API:')]),
u('code', {lang: 'diff'}, [
' var remark = require(\'remark\');',
' var report = require(\'vfile-reporter\');',
'',
' remark()',
' .use(require(\'remark-lint\'))',
'+ .use(require(\'' + basename + '\'))',
' .process(\'_Emphasis_ and **importance**\', function (err, file) {',
' console.error(report(err || file));',
' });'
].join('\n')),
u(
'code',
{lang: 'diff'},
[
" var remark = require('remark');",
" var report = require('vfile-reporter');",
'',
' remark()',
" .use(require('remark-lint'))",
"+ .use(require('" + basename + "'))",
" .process('_Emphasis_ and **importance**', function (err, file) {",
' console.error(report(err || file));',
' });'
].join('\n')
),
u('heading', {depth: 2}, [u('text', 'License')]),
u('paragraph', [
u('link', {url: remote + '/blob/master/LICENSE'}, [u('text', pack.license)]),
u('link', {url: remote + '/blob/master/LICENSE'}, [
u('text', pack.license)
]),
u('text', ' © '),
u('link', {url: author.url}, [u('text', author.name)])
])
]);
])
fs.writeFileSync(
path.join(base, 'readme.md'),
remark().stringify(u('root', children))
);
)
console.log(
chalk.green('✓') + ' wrote `readme.md` in `' + basename + '`'
);
});
console.log(chalk.green('✓') + ' wrote `readme.md` in `' + basename + '`')
})

View File

@ -1,4 +1,4 @@
'use strict';
'use strict'
module.exports = [
{
@ -25,4 +25,4 @@ module.exports = [
out: '\n',
char: '␊'
}
];
]

View File

@ -1,34 +1,37 @@
'use strict';
'use strict'
var path = require('path');
var zone = require('mdast-zone');
var u = require('unist-builder');
var presets = require('../util/presets');
var path = require('path')
var zone = require('mdast-zone')
var u = require('unist-builder')
var presets = require('../util/presets')
var root = path.join(process.cwd(), 'packages');
var root = path.join(process.cwd(), 'packages')
module.exports = listOfPresets;
module.exports = listOfPresets
function listOfPresets() {
return transformer;
return transformer
}
function transformer(tree) {
zone(tree, 'presets', replace);
zone(tree, 'presets', replace)
}
function replace(start, nodes, end) {
var items = presets(root).map(function (basename) {
var pack = require(path.join(root, basename, 'package.json'));
var description = pack.description.replace(/^remark preset to configure remark-lint with ?/i, '');
return [start, u('list', {ordered: false}, presets(root).map(item)), end]
function item(basename) {
var pack = require(path.join(root, basename, 'package.json'))
var description = pack.description.replace(
/^remark preset to configure remark-lint with ?/i,
''
)
return u('listItem', [
u('paragraph', [
u('link', {url: pack.repository}, [u('inlineCode', basename)]),
u('text', ' — ' + description)
])
]);
});
return [start, u('list', {ordered: false}, items), end];
])
}
}

View File

@ -1,35 +1,35 @@
'use strict';
'use strict'
var path = require('path');
var zone = require('mdast-zone');
var u = require('unist-builder');
var rules = require('../util/rules');
var path = require('path')
var zone = require('mdast-zone')
var u = require('unist-builder')
var rules = require('../util/rules')
var root = path.join(process.cwd(), 'packages');
var root = path.join(process.cwd(), 'packages')
module.exports = listOfRules;
module.exports = listOfRules
function listOfRules() {
return transformer;
return transformer
}
function transformer(tree) {
zone(tree, 'rules', replace);
zone(tree, 'rules', replace)
}
function replace(start, nodes, end) {
var items = rules(root).map(function (basename) {
var name = basename.slice('remark-lint-'.length);
var pack = require(path.join(root, basename, 'package.json'));
var description = pack.description.replace(/^remark-lint rule to ?/i, '');
return [start, u('list', {ordered: false}, rules(root).map(item)), end]
function item(basename) {
var name = basename.slice('remark-lint-'.length)
var pack = require(path.join(root, basename, 'package.json'))
var description = pack.description.replace(/^remark-lint rule to ?/i, '')
return u('listItem', [
u('paragraph', [
u('link', {url: pack.repository}, [u('inlineCode', name)]),
u('text', ' — ' + description)
])
]);
});
return [start, u('list', {ordered: false}, items), end];
])
}
}

View File

@ -1,33 +1,33 @@
'use strict';
'use strict'
module.exports = find;
module.exports = find
find.all = findAll;
find.all = findAll
/* Find the first tag in `tags` with a type set to `key`. */
function find(tags, key) {
var value = null;
var value = null
tags.some(function (tag) {
tags.some(function(tag) {
if (tag && tag.type === key) {
value = tag;
value = tag
return true;
return true
}
return false;
});
return false
})
return value && value.string;
return value && value.string
}
/* Find the first tag in `tags` with a type set to `key`. */
function findAll(tags, key) {
return tags
.filter(function (tag) {
return tag && tag.type === key;
.filter(function(tag) {
return tag && tag.type === key
})
.map(function(tag) {
return tag.string
})
.map(function (tag) {
return tag.string;
});
}

View File

@ -1,13 +1,13 @@
'use strict';
'use strict'
var fs = require('fs');
var fs = require('fs')
module.exports = rulesSync;
module.exports = rulesSync
function rulesSync(filePath) {
return fs.readdirSync(filePath).filter(filter);
return fs.readdirSync(filePath).filter(filter)
}
function filter(basename) {
return /remark-preset-lint/.test(basename);
return /remark-preset-lint/.test(basename)
}

View File

@ -1,73 +1,78 @@
'use strict';
'use strict'
var fs = require('fs');
var path = require('path');
var dox = require('dox');
var strip = require('strip-indent');
var trim = require('trim');
var find = require('./find');
var fs = require('fs')
var path = require('path')
var dox = require('dox')
var strip = require('strip-indent')
var trim = require('trim')
var find = require('./find')
module.exports = ruleSync;
module.exports = ruleSync
/* Get information for a rule at `filePath`. */
function ruleSync(filePath) {
var ruleId = path.basename(filePath);
var result = {};
var tests = {};
var description;
var code;
var tags;
var name;
var ruleId = path.basename(filePath)
var result = {}
var tests = {}
var description
var code
var tags
var name
ruleId = ruleId.slice('remark-lint-'.length);
code = fs.readFileSync(path.join(filePath, 'index.js'), 'utf-8');
tags = dox.parseComments(code)[0].tags;
description = find(tags, 'fileoverview');
name = find(tags, 'module');
ruleId = ruleId.slice('remark-lint-'.length)
code = fs.readFileSync(path.join(filePath, 'index.js'), 'utf-8')
tags = dox.parseComments(code)[0].tags
description = find(tags, 'fileoverview')
name = find(tags, 'module')
/* istanbul ignore if */
if (name !== ruleId) {
throw new Error(
ruleId + ' has an invalid `@module`: ' + name
);
throw new Error(ruleId + ' has an invalid `@module`: ' + name)
}
/* istanbul ignore if */
if (!description) {
throw new Error(ruleId + ' is missing a `@fileoverview`');
throw new Error(ruleId + ' is missing a `@fileoverview`')
}
description = strip(description);
description = strip(description)
result.ruleId = ruleId;
result.description = trim(description);
result.tests = tests;
result.filePath = filePath;
result.ruleId = ruleId
result.description = trim(description)
result.tests = tests
result.filePath = filePath
find.all(tags, 'example').map(strip).forEach(function (example) {
var lines = example.split('\n');
var value = strip(lines.slice(1).join('\n'));
var info;
var setting;
var context;
var name;
find
.all(tags, 'example')
.map(strip)
.forEach(check)
return result
function check(example) {
var lines = example.split('\n')
var value = strip(lines.slice(1).join('\n'))
var info
var setting
var context
var name
try {
info = JSON.parse(lines[0]);
info = JSON.parse(lines[0])
} catch (err) {
/* istanbul ignore next */
throw new Error(
'Could not parse example in ' + ruleId + ':\n' + err.stack
);
)
}
setting = JSON.stringify(info.setting || true);
context = tests[setting];
name = info.name;
setting = JSON.stringify(info.setting || true)
context = tests[setting]
name = info.name
if (!context) {
context = [];
tests[setting] = context;
context = []
tests[setting] = context
}
if (!info.label) {
@ -76,31 +81,32 @@ function ruleSync(filePath) {
setting: setting,
input: value,
output: []
};
}
return;
return
}
/* istanbul ignore if */
if (info.label !== 'input' && info.label !== 'output') {
throw new Error(
'Expected `input` or `ouput` for `label` in ' +
ruleId + ', not `' + info.label + '`'
);
ruleId +
', not `' +
info.label +
'`'
)
}
if (!context[name]) {
context[name] = {config: info.config || {}};
context[name] = {config: info.config || {}}
}
context[name].setting = setting;
context[name].setting = setting
if (info.label === 'output') {
value = value.split('\n');
value = value.split('\n')
}
context[name][info.label] = value;
});
return result;
context[name][info.label] = value
}
}

View File

@ -1,13 +1,13 @@
'use strict';
'use strict'
var fs = require('fs');
var fs = require('fs')
module.exports = rulesSync;
module.exports = rulesSync
function rulesSync(filePath) {
return fs.readdirSync(filePath).filter(filter);
return fs.readdirSync(filePath).filter(filter)
}
function filter(basename) {
return /remark-lint/.test(basename) && basename !== 'remark-lint';
return /remark-lint/.test(basename) && basename !== 'remark-lint'
}

452
test.js
View File

@ -1,22 +1,22 @@
'use strict';
'use strict'
/* eslint-disable max-params */
var path = require('path');
var test = require('tape');
var vfile = require('to-vfile');
var removePosition = require('unist-util-remove-position');
var remark = require('remark');
var rules = require('./script/util/rules');
var rule = require('./script/util/rule');
var lint = require('./packages/remark-lint');
var noHeadingPunctuation = require('./packages/remark-lint-no-heading-punctuation');
var noMultipleToplevelHeadings = require('./packages/remark-lint-no-multiple-toplevel-headings');
var finalNewline = require('./packages/remark-lint-final-newline');
var chars = require('./script/characters');
var path = require('path')
var test = require('tape')
var vfile = require('to-vfile')
var removePosition = require('unist-util-remove-position')
var remark = require('remark')
var rules = require('./script/util/rules')
var rule = require('./script/util/rule')
var lint = require('./packages/remark-lint')
var noHeadingPunctuation = require('./packages/remark-lint-no-heading-punctuation')
var noMultipleToplevelHeadings = require('./packages/remark-lint-no-multiple-toplevel-headings')
var finalNewline = require('./packages/remark-lint-final-newline')
var chars = require('./script/characters')
test('core', function (t) {
t.test('should work', function (st) {
test('core', function(t) {
t.test('should work', function(st) {
var doc = [
'# A heading',
'',
@ -25,330 +25,340 @@ test('core', function (t) {
'<!--lint ignore-->',
'',
'# Another main heading.'
].join('\n');
].join('\n')
st.plan(4);
st.plan(2)
remark()
.use(noHeadingPunctuation)
.use(noMultipleToplevelHeadings)
.use(lint)
.process(vfile({path: 'virtual.md', contents: doc}), function (err, file) {
st.ifErr(err, 'should not fail');
.process(vfile({path: 'virtual.md', contents: doc}), function(err, file) {
st.deepEqual(
file.messages.map(String),
[err].concat(file.messages.map(String)),
[
null,
'virtual.md:3:1-3:24: Dont add a trailing `.` to headings',
'virtual.md:3:1-3:24: Dont use multiple top level headings (3:1)'
],
'should support `remark-lint` last'
);
});
)
})
remark()
.use(lint)
.use(noHeadingPunctuation)
.use(noMultipleToplevelHeadings)
.process(vfile({path: 'virtual.md', contents: doc}), function (err, file) {
st.ifErr(err, 'should not fail');
.process(vfile({path: 'virtual.md', contents: doc}), function(err, file) {
st.deepEqual(
file.messages.map(String),
[err].concat(file.messages.map(String)),
[
null,
'virtual.md:3:1-3:24: Dont add a trailing `.` to headings',
'virtual.md:3:1-3:24: Dont use multiple top level headings (3:1)'
],
'should support `remark-lint` first'
);
});
});
)
})
})
t.test('should support no rules', function (st) {
st.plan(2);
t.test('should support no rules', function(st) {
st.plan(1)
remark().use(lint).process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.deepEqual(
file.messages.map(String),
[],
'should warn for missing new lines'
);
});
});
remark()
.use(lint)
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null],
'should warn for missing new lines'
)
})
})
t.test('should support successful rules', function (st) {
st.plan(2);
t.test('should support successful rules', function(st) {
st.plan(1)
remark()
.use(finalNewline)
.process('', function (err, file) {
st.ifErr(err, 'should not fail');
st.deepEqual(file.messages, [], 'should support successful rules');
});
});
.process('', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null],
'should support successful rules'
)
})
})
t.test('should support a list with a severity', function (st) {
st.plan(3);
t.test('should support a list with a severity', function(st) {
st.plan(2)
remark()
.use(finalNewline, [2])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.equal(
file.messages.join(),
'1:1: Missing newline character at end of file',
'should trigger fatally (1)'
);
st.equal(file.messages[0].fatal, true, 'should trigger fatally (2)');
});
});
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null, '1:1: Missing newline character at end of file'],
'should emit fatally (1)'
)
st.equal(file.messages[0].fatal, true, 'should emit fatally (2)')
})
})
t.test('should support a boolean (`true`)', function (st) {
t.test('should support a boolean (`true`)', function(st) {
/* Note! This is handled by unified. */
st.plan(2);
st.plan(1)
remark()
.use(finalNewline, true)
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.equal(
file.messages.join(),
'1:1: Missing newline character at end of file',
'should trigger'
);
});
});
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null, '1:1: Missing newline character at end of file'],
'should emit'
)
})
})
t.test('should support a boolean (`false`)', function (st) {
t.test('should support a boolean (`false`)', function(st) {
/* Note! This is handled by unified. */
st.plan(2);
st.plan(1)
remark()
.use(finalNewline, false)
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.deepEqual(file.messages, [], 'should not trigger');
});
});
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null],
'should not emit'
)
})
})
t.test('should support a list with a boolean severity (true, for on)', function (st) {
st.plan(2);
t.test(
'should support a list with a boolean severity (true, for on)',
function(st) {
st.plan(1)
remark()
.use(finalNewline, [true])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.equal(
file.messages.join(),
'1:1: Missing newline character at end of file',
'should trigger'
);
});
});
remark()
.use(finalNewline, [true])
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null, '1:1: Missing newline character at end of file'],
'should emit'
)
})
}
)
t.test('should support a list with a boolean severity (false, for off)', function (st) {
st.plan(2);
t.test(
'should support a list with boolean severity (false, for off)',
function(st) {
st.plan(1)
remark()
.use(finalNewline, [false])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.deepEqual(file.messages, [], 'should not trigger');
});
});
remark()
.use(finalNewline, [false])
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null],
'should not emit'
)
})
}
)
t.test('should support a list with a string severity (`error`)', function (st) {
st.plan(3);
t.test('should support a list with string severity (`error`)', function(st) {
st.plan(2)
remark()
.use(finalNewline, ['error'])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.equal(
file.messages.join(),
'1:1: Missing newline character at end of file',
'should trigger fatally (1)'
);
st.equal(file.messages[0].fatal, true, 'should trigger fatally (2)');
});
});
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null, '1:1: Missing newline character at end of file'],
'should emit fatally (1)'
)
st.equal(file.messages[0].fatal, true, 'should emit fatally (2)')
})
})
t.test('should support a list with a string severity (`on`)', function (st) {
st.plan(3);
t.test('should support a list with a string severity (`on`)', function(st) {
st.plan(2)
remark()
.use(finalNewline, ['on'])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.equal(
file.messages.join(),
'1:1: Missing newline character at end of file',
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null, '1:1: Missing newline character at end of file'],
'should message'
);
st.equal(file.messages[0].fatal, false, 'should *not* trigger fatally');
});
});
)
st.equal(file.messages[0].fatal, false, 'should not emit fatally')
})
})
t.test('should support a list with a string severity (`warn`)', function (st) {
st.plan(3);
t.test('should support a list with a string severity (`warn`)', function(st) {
st.plan(2)
remark()
.use(finalNewline, ['warn'])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.equal(
file.messages.join(),
'1:1: Missing newline character at end of file',
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null, '1:1: Missing newline character at end of file'],
'should message'
);
st.equal(file.messages[0].fatal, false, 'should *not* trigger fatally');
});
});
)
st.equal(file.messages[0].fatal, false, 'should not emit fatally')
})
})
t.test('should support a list with a string severity (`off`)', function (st) {
st.plan(2);
t.test('should support a list with a string severity (`off`)', function(st) {
st.plan(1)
remark()
.use(finalNewline, ['off'])
.process('.', function (err, file) {
st.ifErr(err, 'should not fail');
st.deepEqual(file.messages, [], 'should disable `final-newline`');
});
});
.process('.', function(err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[null],
'should disable `final-newline`'
)
})
})
t.test('should fail on invalid severities', function (st) {
t.test('should fail on invalid severities', function(st) {
st.throws(
function () {
remark().use(finalNewline, [3]).freeze();
function() {
remark()
.use(finalNewline, [3])
.freeze()
},
/^Error: Invalid severity `3` for `final-newline`, expected 0, 1, or 2$/,
'should throw when too high'
);
)
st.throws(
function () {
remark().use(finalNewline, [-1]).freeze();
function() {
remark()
.use(finalNewline, [-1])
.freeze()
},
/^Error: Invalid severity `-1` for `final-newline`, expected 0, 1, or 2$/,
'should throw too low'
);
)
st.end();
});
st.end()
})
t.end();
});
t.end()
})
test('rules', function (t) {
var root = path.join(process.cwd(), 'packages');
var all = rules(root);
test('rules', function(t) {
var root = path.join(process.cwd(), 'packages')
var all = rules(root)
t.plan(all.length);
t.plan(all.length)
all.forEach(each);
all.forEach(each)
function each(basename) {
var base = path.resolve(root, basename);
var info = rule(base);
var fn = require(base);
var base = path.resolve(root, basename)
var info = rule(base)
var fn = require(base)
t.test(info.ruleId, one);
t.test(info.ruleId, one)
function one(sst) {
assertRule(sst, fn, info);
function one(st) {
assertRule(st, fn, info)
}
}
});
})
/* Assert a rule. */
function assertRule(t, rule, info) {
var tests = info.tests;
var tests = info.tests
Object.keys(tests).forEach(function (setting) {
var fixture = tests[setting];
var config = JSON.parse(setting);
Object.keys(tests).forEach(function(setting) {
var fixture = tests[setting]
var config = JSON.parse(setting)
t.test(setting, function (st) {
Object.keys(fixture).forEach(function (name) {
st.test(name, function (sst) {
assertFixture(sst, rule, info, fixture[name], name, config);
});
});
});
});
t.test(setting, function(st) {
Object.keys(fixture).forEach(function(name) {
st.test(name, function(sst) {
assertFixture(sst, rule, info, fixture[name], name, config)
})
})
})
})
t.end();
t.end()
}
function assertFixture(t, rule, info, fixture, basename, setting) {
var ruleId = info.ruleId;
var file = vfile(basename);
var expected = fixture.output;
var positionless = fixture.config.positionless;
var proc = remark().use(rule, setting).data('settings', fixture.config);
var ruleId = info.ruleId
var file = vfile(basename)
var expected = fixture.output
var positionless = fixture.config.positionless
var proc = remark()
.use(rule, setting)
.data('settings', fixture.config)
file.contents = preprocess(fixture.input || '');
file.contents = preprocess(fixture.input || '')
t.plan(positionless ? 1 : 2);
t.plan(positionless ? 1 : 2)
try {
proc.runSync(proc.parse(file), file);
proc.runSync(proc.parse(file), file)
} catch (err) {
if (err && err.source !== 'remark-lint') {
throw err;
throw err
}
}
t.deepEqual(
normalize(file.messages),
expected,
'should equal with position'
);
file.messages.forEach(function (message) {
file.messages.forEach(function(message) {
if (message.ruleId !== ruleId) {
throw new Error(
'Expected `' + ruleId + '`, not `' +
message.ruleId + '` as `ruleId` for ' +
message
);
'Expected `' +
ruleId +
'`, not `' +
message.ruleId +
'` as `ruleId` for ' +
message
)
}
});
})
t.deepEqual(normalize(file.messages), expected, 'should equal with position')
if (!positionless) {
file.messages = [];
file.messages = []
try {
remark()
.use(function () {
return removePosition;
})
.use(rule, setting)
.processSync(file);
} catch (err) {
console.log('err: ', err);
}
remark()
.use(clear)
.use(rule, setting)
.processSync(file)
t.deepEqual(
normalize(file.messages),
[],
'should equal without position'
);
t.deepEqual(normalize(file.messages), [], 'should equal without position')
}
file.messages = [];
function clear() {
return removePosition
}
}
function normalize(messages) {
return messages.map(function (message) {
var value = String(message);
return value.slice(value.indexOf(':') + 1);
});
return messages.map(function(message) {
var value = String(message)
return value.slice(value.indexOf(':') + 1)
})
}
function preprocess(value) {
chars.forEach(function (char) {
value = value.replace(char.in, char.out);
});
chars.forEach(function(char) {
value = value.replace(char.in, char.out)
})
return value;
return value
}