Skip to content

Commit

Permalink
Add support for external rules
Browse files Browse the repository at this point in the history
*   Add example rule, and rule object file, to
    `test/external`;
*   Add fixtures for external tests;
*   Move `mdast.js`, `mdast.min.js` to `mdast-lint.js`,
    `mdast-lint.min.js`;
*   Add docs for external rules.

Closes GH-14.
  • Loading branch information
wooorm committed Jun 12, 2015
1 parent 6d2ba65 commit 457a17c
Show file tree
Hide file tree
Showing 12 changed files with 310 additions and 8 deletions.
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
86 changes: 85 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 defaultRules = 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,79 @@ 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 `$package` is an ancestral package directory and
* `$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(defaultRules);
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 +188,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
90 changes: 87 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 defaultRules = 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,79 @@ 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 `$package` is an ancestral package directory and
* `$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(defaultRules);
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 +268,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 +406,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

0 comments on commit 457a17c

Please sign in to comment.