Add synchronous version of the API, with tests and documentation.

This commit is contained in:
David Anson 2015-03-20 00:09:55 -07:00
parent 200dddf3d1
commit e557f3a97f
6 changed files with 132 additions and 18 deletions

View file

@ -87,7 +87,7 @@
"no-space-before-semi": 2, "no-space-before-semi": 2,
"no-spaced-func": 2, "no-spaced-func": 2,
"no-sparse-arrays": 2, "no-sparse-arrays": 2,
"no-sync": 2, "no-sync": 0,
"no-ternary": 0, "no-ternary": 0,
"no-trailing-spaces": 2, "no-trailing-spaces": 2,
"no-throw-literal": 2, "no-throw-literal": 2,

View file

@ -87,6 +87,8 @@ See [Rules.md](doc/Rules.md) for more details.
## API ## API
Standard asynchronous interface:
```js ```js
/** /**
* Lint specified Markdown files according to configurable rules. * Lint specified Markdown files according to configurable rules.
@ -98,6 +100,18 @@ See [Rules.md](doc/Rules.md) for more details.
function markdownlint(options, callback) { ... } function markdownlint(options, callback) { ... }
``` ```
Synchronous interface (for build scripts, etc.):
```js
/**
* Lint specified Markdown files according to configurable rules.
*
* @param {Object} options Configuration options.
* @returns {Object} Result object.
*/
function markdownlint.sync(options) { ... }
```
### options ### options
Type: `Object` Type: `Object`
@ -161,9 +175,14 @@ See the [style](style) directory for more samples.
Type: `Function` taking (`Error`, `Object`) Type: `Function` taking (`Error`, `Object`)
Function to call upon completion. Standard completion callback.
See below for an example of the structure of the `result` object. ### result
Type: `Object`
Call `result.toString()` for convenience or see below for an example of the
structure of the `result` object.
## Usage ## Usage
@ -183,7 +202,14 @@ markdownlint(options, function callback(err, result) {
}); });
``` ```
Outputs: Or invoke `markdownlint.sync` for a synchronous call:
```js
var result = markdownlint.sync(options);
console.log(result.toString());
```
Output of both calls:
```text ```text
bad.md: 3: MD010 Hard tabs bad.md: 3: MD010 Hard tabs
@ -191,7 +217,7 @@ bad.md: 1: MD018 No space after hash on atx style header
bad.md: 3: MD018 No space after hash on atx style header bad.md: 3: MD018 No space after hash on atx style header
``` ```
Or examine the `result` object directly: To examine the `result` object directly:
```js ```js
markdownlint(options, function callback(err, result) { markdownlint(options, function callback(err, result) {
@ -201,7 +227,7 @@ markdownlint(options, function callback(err, result) {
}); });
``` ```
Outputs: Output:
```json ```json
{ {
@ -236,7 +262,7 @@ gulp.task("markdownlint", function task() {
}); });
``` ```
Outputs: Output:
```text ```text
[00:00:00] Starting 'markdownlint'... [00:00:00] Starting 'markdownlint'...
@ -275,7 +301,7 @@ module.exports = function wrapper(grunt) {
}; };
``` ```
Outputs: Output:
```text ```text
Running "markdownlint:example" (markdownlint) task Running "markdownlint:example" (markdownlint) task

View file

@ -19,3 +19,7 @@ markdownlint(options, function callback(err, result) {
console.dir(result, { "colors": true }); console.dir(result, { "colors": true });
} }
}); });
// Make a synchronous call
var result = markdownlint.sync(options);
console.log(result.toString());

View file

@ -54,8 +54,9 @@ function uniqueFilterForSorted(value, index, array) {
} }
// Lints a single file // Lints a single file
function lintFile(file, config, callback) { function lintFile(file, config, synchronous, callback) {
fs.readFile(file, shared.utf8Encoding, function readFile(err, contents) { // Callback for read file API
function readFile(err, contents) {
if (err) { if (err) {
callback(err); callback(err);
} else { } else {
@ -118,28 +119,43 @@ function lintFile(file, config, callback) {
}); });
callback(null, result); callback(null, result);
} }
}); }
// Make a/synchronous call to read file
if (synchronous) {
readFile(null, fs.readFileSync(file, shared.utf8Encoding));
} else {
fs.readFile(file, shared.utf8Encoding, readFile);
}
}
// Callback used as a sentinel by markdownlintSync
function markdownlintSynchronousCallback() {
// Unreachable; no code path in the synchronous case passes err
// if (err) {
// throw err; // Synchronous APIs throw
// }
} }
/** /**
* Lint specified Markdown files according to configurable rules. * Lint specified Markdown files according to configurable rules.
* *
* @param {Object} options Configuration options. * @param {Object} options Configuration options.
* @param {Function} callback Callback (err, results) function. * @param {Function} callback Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
module.exports = function markdownlint(options, callback) { function markdownlint(options, callback) {
// Normalize inputs // Normalize inputs
options = options || {}; options = options || {};
callback = callback || function noop() {}; callback = callback || function noop() {};
var files = (options.files || []).slice(); var files = (options.files || []).slice();
var config = options.config || { "default": true }; var config = options.config || { "default": true };
var synchronous = (callback === markdownlintSynchronousCallback);
var results = new Results(); var results = new Results();
// Lint each input file // Lint each input file
function lintFiles() { function lintFiles() {
var file = files.shift(); var file = files.shift();
if (file) { if (file) {
lintFile(file, config, function lintFileCallback(err, result) { lintFile(file, config, synchronous, function lintedFile(err, result) {
if (err) { if (err) {
callback(err); callback(err);
} else { } else {
@ -153,4 +169,21 @@ module.exports = function markdownlint(options, callback) {
} }
} }
lintFiles(); lintFiles();
}; if (synchronous) {
return results;
}
}
/**
* Lint specified Markdown files according to configurable rules.
*
* @param {Object} options Configuration options.
* @returns {Object} Result object.
*/
function markdownlintSync(options) {
return markdownlint(options, markdownlintSynchronousCallback);
}
// Export a/synchronous APIs
module.exports = markdownlint;
module.exports.sync = markdownlintSync;

View file

@ -17,7 +17,7 @@
"test": "nodeunit", "test": "nodeunit",
"test-cover": "istanbul cover node_modules/nodeunit/bin/nodeunit", "test-cover": "istanbul cover node_modules/nodeunit/bin/nodeunit",
"debug": "node debug node_modules/nodeunit/bin/nodeunit", "debug": "node debug node_modules/nodeunit/bin/nodeunit",
"lint": "eslint lib test & eslint --rule \"no-console: 0\" example", "lint": "eslint lib test & eslint --rule \"no-console: 0, no-shadow: 0\" example",
"example": "cd example & node standalone.js & grunt markdownlint & gulp markdownlint" "example": "cd example & node standalone.js & grunt markdownlint & gulp markdownlint"
}, },
"dependencies": { "dependencies": {

View file

@ -65,7 +65,7 @@ function createTestForFile(file) {
}; };
} }
fs.readdirSync("./test").forEach(function forFile(file) { // eslint-disable-line fs.readdirSync("./test").forEach(function forFile(file) {
if (file.match(/\.md$/)) { if (file.match(/\.md$/)) {
module.exports[file] = createTestForFile(path.join("./test", file)); module.exports[file] = createTestForFile(path.join("./test", file));
} }
@ -122,6 +122,42 @@ module.exports.resultFormatting = function resultFormatting(test) {
}); });
}; };
module.exports.resultFormattingSync = function resultFormattingSync(test) {
test.expect(2);
var options = {
"files": [
"./test/atx_header_spacing.md",
"./test/first_header_bad_atx.md"
]
};
var actualResult = markdownlint.sync(options);
var expectedResult = {
"./test/atx_header_spacing.md": {
"MD002": [ 3 ],
"MD018": [ 1 ],
"MD019": [ 3, 5 ]
},
"./test/first_header_bad_atx.md": {
"MD002": [ 1 ]
}
};
test.deepEqual(actualResult, expectedResult, "Undetected issues.");
var actualMessage = actualResult.toString();
var expectedMessage =
"./test/atx_header_spacing.md: 3: MD002" +
" First header should be a h1 header\n" +
"./test/atx_header_spacing.md: 1: MD018" +
" No space after hash on atx style header\n" +
"./test/atx_header_spacing.md: 3: MD019" +
" Multiple spaces after hash on atx style header\n" +
"./test/atx_header_spacing.md: 5: MD019" +
" Multiple spaces after hash on atx style header\n" +
"./test/first_header_bad_atx.md: 1: MD002" +
" First header should be a h1 header";
test.equal(actualMessage, expectedMessage, "Incorrect message.");
test.done();
};
module.exports.defaultTrue = function defaultTrue(test) { module.exports.defaultTrue = function defaultTrue(test) {
test.expect(2); test.expect(2);
var options = { var options = {
@ -435,17 +471,32 @@ module.exports.missingCallback = function missingCallback(test) {
}; };
module.exports.badFile = function badFile(test) { module.exports.badFile = function badFile(test) {
test.expect(3); test.expect(4);
markdownlint({ markdownlint({
"files": [ "./badFile" ] "files": [ "./badFile" ]
}, function callback(err, result) { }, function callback(err, result) {
test.ok(err, "Did not get an error for bad file."); test.ok(err, "Did not get an error for bad file.");
test.ok(err instanceof Error, "Error not instance of Error.");
test.equal(err.code, "ENOENT", "Error code for bad file not ENOENT."); test.equal(err.code, "ENOENT", "Error code for bad file not ENOENT.");
test.ok(!result, "Got result for bad file."); test.ok(!result, "Got result for bad file.");
test.done(); test.done();
}); });
}; };
module.exports.badFileSync = function badFileSync(test) {
test.expect(3);
test.throws(function badFileCall() {
markdownlint.sync({
"files": [ "./badFile" ]
});
}, function testError(err) {
test.ok(err instanceof Error, "Error not instance of Error.");
test.equal(err.code, "ENOENT", "Error code for bad file not ENOENT.");
return true;
}, "Did not get exception for bad file.");
test.done();
};
module.exports.readme = function readme(test) { module.exports.readme = function readme(test) {
test.expect(143); test.expect(143);
fs.readFile("README.md", shared.utf8Encoding, fs.readFile("README.md", shared.utf8Encoding,