Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for external rules #15

Merged
merged 1 commit into from
Jun 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion doc/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fix their warnings.

* [Rules](#rules)

* [externals](#externals)
* [reset](#reset)
* [blockquote-indentation](#blockquote-indentation)
* [code-block-style](#code-block-style)
Expand Down Expand Up @@ -69,10 +70,27 @@ Remember that rules can always be turned off by
passing false. In addition, when reset is given, values can
be null or undefined in order to be ignored.

### externals

````md
<!-- Load more rules -->
```json
{
"externals": ["foo", "bar", "baz"]
}
```
````

Externals contains a list of extra rules to load.
These are, or refer to, an object mapping `ruleId`s to rules.

Note that in node.js, a string can be given (a module
name or a file), but in the browser an object must be passed in.

### reset

````md
<!-- Explicitly activate rules: -->
<!-- Explicitly activate rules: -->
```json
{
"reset": true,
Expand Down
85 changes: 84 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@

var range = require('mdast-range');
var zone = require('mdast-zone');
var rules = require('./rules');
var internals = require('./rules');
var sort = require('./sort');
var filter = require('./filter');

/*
* Needed for plug-in resolving.
*/

var path = require('path');
var fs = require('fs');
var exists = fs && fs.existsSync;
var resolve = path && path.resolve;
var cwd = process && process.cwd();

/**
* Factory to create a plugin from a rule.
*
Expand Down Expand Up @@ -85,6 +95,78 @@ function attachFactory(id, rule, options) {
return attach;
}

/**
* Require an external. Checks, in this order:
*
* - `$cwd/$pathlike`;
* - `$cwd/$pathlike.js`;
* - `$cwd/node_modules/$pathlike`;
* - `$pathlike`.
*
* Where `$cwd` is the current working directory.
*
* @example
* var plugin = findPlugin('foo');
*
* @throws {Error} - Fails when `pathlike` cannot be
* resolved.
* @param {string} pathlike - Reference to external.
* @return {Object} - Result of `require`ing external.
*/
function loadExternal(pathlike) {
var local = resolve(cwd, pathlike);
var current = resolve(cwd, 'node_modules', pathlike);
var plugin;

if (exists(local) || exists(local + '.js')) {
plugin = local;
/* istanbul ignore else - for globals */
} else if (exists(current)) {
plugin = current;
} else {
plugin = pathlike;
}

return require(plugin);
}

/**
* Load all externals. Merges them into a single rule
* object.
*
* In node, accepts externals as strings, otherwise,
* externals should be a list of objects.
*
* @param {Array.<string|Object>} externals
* @return {Array.<Object>}
* @throws {Error} - When an external cannot be resolved.
*/
function loadExternals(externals) {
var index = -1;
var rules = {};
var external;
var ruleId;
var mapping = externals ? externals.concat() : [];
var length;

mapping.push(internals);
length = mapping.length;

while (++index < length) {
external = mapping[index];

if (typeof external === 'string') {
external = loadExternal(external);
}

for (ruleId in external) {
rules[ruleId] = external[ruleId];
}
}

return rules;
}

/**
* Lint attacher.
*
Expand All @@ -105,6 +187,7 @@ function attachFactory(id, rule, options) {
function lint(mdast, options) {
var settings = options || {};
var reset = settings.reset;
var rules = loadExternals(settings.external);
var id;
var setting;

Expand Down
89 changes: 86 additions & 3 deletions mdast.js → mdast-lint.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.mdast = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.mdastLint = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';

module.exports = require('./lib');
Expand Down Expand Up @@ -94,10 +94,20 @@ module.exports = attacher;

var range = require('mdast-range');
var zone = require('mdast-zone');
var rules = require('./rules');
var internals = require('./rules');
var sort = require('./sort');
var filter = require('./filter');

/*
* Needed for plug-in resolving.
*/

var path = require('path');
var fs = require('fs');
var exists = fs && fs.existsSync;
var resolve = path && path.resolve;
var cwd = process && process.cwd();

/**
* Factory to create a plugin from a rule.
*
Expand Down Expand Up @@ -165,6 +175,78 @@ function attachFactory(id, rule, options) {
return attach;
}

/**
* Require an external. Checks, in this order:
*
* - `$cwd/$pathlike`;
* - `$cwd/$pathlike.js`;
* - `$cwd/node_modules/$pathlike`;
* - `$pathlike`.
*
* Where `$cwd` is the current working directory.
*
* @example
* var plugin = findPlugin('foo');
*
* @throws {Error} - Fails when `pathlike` cannot be
* resolved.
* @param {string} pathlike - Reference to external.
* @return {Object} - Result of `require`ing external.
*/
function loadExternal(pathlike) {
var local = resolve(cwd, pathlike);
var current = resolve(cwd, 'node_modules', pathlike);
var plugin;

if (exists(local) || exists(local + '.js')) {
plugin = local;
/* istanbul ignore else - for globals */
} else if (exists(current)) {
plugin = current;
} else {
plugin = pathlike;
}

return require(plugin);
}

/**
* Load all externals. Merges them into a single rule
* object.
*
* In node, accepts externals as strings, otherwise,
* externals should be a list of objects.
*
* @param {Array.<string|Object>} externals
* @return {Array.<Object>}
* @throws {Error} - When an external cannot be resolved.
*/
function loadExternals(externals) {
var index = -1;
var rules = {};
var external;
var ruleId;
var mapping = externals ? externals.concat() : [];
var length;

mapping.push(internals);
length = mapping.length;

while (++index < length) {
external = mapping[index];

if (typeof external === 'string') {
external = loadExternal(external);
}

for (ruleId in external) {
rules[ruleId] = external[ruleId];
}
}

return rules;
}

/**
* Lint attacher.
*
Expand All @@ -185,6 +267,7 @@ function attachFactory(id, rule, options) {
function lint(mdast, options) {
var settings = options || {};
var reset = settings.reset;
var rules = loadExternals(settings.external);
var id;
var setting;

Expand Down Expand Up @@ -322,7 +405,7 @@ function lint(mdast, options) {

module.exports = lint;

},{"./filter":2,"./rules":18,"./sort":58,"mdast-range":64,"mdast-zone":65}],4:[function(require,module,exports){
},{"./filter":2,"./rules":18,"./sort":58,"fs":undefined,"mdast-range":64,"mdast-zone":65,"path":undefined}],4:[function(require,module,exports){
/**
* @author Titus Wormer
* @copyright 2015 Titus Wormer. All rights reserved.
Expand Down
1 change: 1 addition & 0 deletions mdast-lint.min.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion mdast.min.js

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
"make": "npm run lint && npm run test-coverage",
"build-rules": "node script/build-rule-documentation.js",
"build-md": "mdast . LICENSE --output",
"build-bundle": "browserify index.js -s mdast > mdast.js",
"postbuild-bundle": "esmangle mdast.js > mdast.min.js",
"build-bundle": "browserify index.js --bare -s mdastLint > mdast-lint.js",
"postbuild-bundle": "esmangle mdast-lint.js > mdast-lint.min.js",
"build": "npm run build-rules && npm run build-md && npm run build-bundle",
"prepublish": "npm run build"
}
Expand Down
4 changes: 4 additions & 0 deletions script/additional.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
"reset": {
"description": "By default, all rules are turned on unless explicitly set to `false`.\nWhen `reset: true`, the opposite is true: all rules are turned off,\nunless when given a non-nully and non-false value.\n\nOptions: `boolean`, default: `false`.",
"example": " <!-- Explicitly activate rules: -->\n ```json\n {\n \"reset\": true,\n \"final-newline\": true\n }\n ```\n"
},
"externals": {
"description": "Externals contains a list of extra rules to load.\nThese are, or refer to, an object mapping `ruleId`s to rules.\n\nNote that in node.js, a string can be given (a module\nname or a file), but in the browser an object must be passed in.",
"example": " <!-- Load more rules -->\n ```json\n {\n \"externals\": [\"foo\", \"bar\", \"baz\"]\n }\n ```\n"
}
}
16 changes: 16 additions & 0 deletions test/external/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @author Titus Wormer
* @copyright 2015 Titus Wormer. All rights reserved.
* @module External
* @fileoverview Map of example external rules.
*/

'use strict';

/*
* Expose.
*/

module.exports = {
'no-lorem': require('./no-lorem')
};
40 changes: 40 additions & 0 deletions test/external/no-lorem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @author Titus Wormer
* @copyright 2015 Titus Wormer. All rights reserved.
* @module no-tabs
* @fileoverview
* Warn when `lorem` is used in a document.
* @example
* <!-- Invalid: -->
* lorem
*
* <!-- Valid: -->
* ipsum
*/

'use strict';

/**
* Warn when `lorem` is used in a document.
*
* @param {Node} ast - Root node.
* @param {File} file - Virtual file.
* @param {*} preferred - Ignored.
* @param {Function} done - Callback.
*/
function noLorem(ast, file, preferred, done) {
var content = file.toString();
var expression = /\blorem\b/gi;

while (expression.exec(content)) {
file.warn('Do not use lorem', file.offsetToPosition(expression.lastIndex));
}

done();
}

/*
* Expose.
*/

module.exports = noLorem;
1 change: 1 addition & 0 deletions test/fixtures/lorem-invalid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Lorem.
1 change: 1 addition & 0 deletions test/fixtures/lorem-valid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ipsum.
Loading