Add Promise-based APIs for markdownlint and readConfig, update declaration file.

This commit is contained in:
David Anson 2020-09-13 12:58:09 -07:00
parent 1f6a2cdc96
commit e9d63a6284
5 changed files with 201 additions and 45 deletions

View file

@ -235,31 +235,44 @@ located. Multiple such comments (if present) are applied top-to-bottom.
### Linting ### Linting
Standard asynchronous interface: Standard asynchronous API:
```js ```js
/** /**
* Lint specified Markdown files. * Lint specified Markdown files.
* *
* @param {Object} options Configuration options. * @param {Options} options Configuration options.
* @param {Function} callback Callback (err, result) function. * @param {LintCallback} callback Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
function markdownlint(options, callback) { ... } function markdownlint(options, callback) { ... }
``` ```
Synchronous interface (for build scripts, etc.): Synchronous API (for build scripts, etc.):
```js ```js
/** /**
* Lint specified Markdown files synchronously. * Lint specified Markdown files synchronously.
* *
* @param {Object} options Configuration options. * @param {Options} options Configuration options.
* @returns {Object} Result object. * @returns {LintResults} Results object.
*/ */
function markdownlint.sync(options) { ... } function markdownlint.sync(options) { ... }
``` ```
Promise API (in the `promises` namespace like Node.js's
[`fs` Promises API](https://nodejs.org/api/fs.html#fs_fs_promises_api)):
```js
/**
* Lint specified Markdown files.
*
* @param {Options} options Configuration options.
* @returns {Promise<LintResults>} Results object.
*/
function markdownlint(options) { ... }
```
#### options #### options
Type: `Object` Type: `Object`
@ -518,33 +531,47 @@ other files (see above).
By default, configuration files are parsed as JSON (and named `.markdownlint.json`). By default, configuration files are parsed as JSON (and named `.markdownlint.json`).
Custom parsers can be provided to handle other formats like JSONC, YAML, and TOML. Custom parsers can be provided to handle other formats like JSONC, YAML, and TOML.
Asynchronous interface: Asynchronous API:
```js ```js
/** /**
* Read specified configuration file. * Read specified configuration file.
* *
* @param {String} file Configuration file name/path. * @param {string} file Configuration file name.
* @param {Array} [parsers] Optional parsing function(s). * @param {ConfigurationParser[] | ReadConfigCallback} parsers Parsing function(s).
* @param {Function} callback Callback (err, result) function. * @param {ReadConfigCallback} [callback] Callback (err, result) function.
* @returns {void} * @returns {void}
*/ */
function readConfig(file, parsers, callback) { ... } function readConfig(file, parsers, callback) { ... }
``` ```
Synchronous interface: Synchronous API:
```js ```js
/** /**
* Read specified configuration file synchronously. * Read specified configuration file synchronously.
* *
* @param {String} file Configuration file name/path. * @param {string} file Configuration file name.
* @param {Array} [parsers] Optional parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @returns {Object} Configuration object. * @returns {Configuration} Configuration object.
*/ */
function readConfigSync(file, parsers) { ... } function readConfigSync(file, parsers) { ... }
``` ```
Promise API (in the `promises` namespace like Node.js's
[`fs` Promises API](https://nodejs.org/api/fs.html#fs_fs_promises_api)):
```js
/**
* Read specified configuration file.
*
* @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @returns {Promise<Configuration>} Configuration object.
*/
function readConfig(file, parsers) { ... }
```
#### file #### file
Type: `String` Type: `String`

View file

@ -64,11 +64,14 @@ function assertLintResultsCallback(err: Error | null, results?: markdownlint.Lin
assertConfiguration(markdownlint.readConfigSync(markdownlintJsonPath)); assertConfiguration(markdownlint.readConfigSync(markdownlintJsonPath));
assertConfiguration(markdownlint.readConfigSync(markdownlintJsonPath, [ JSON.parse ])); assertConfiguration(markdownlint.readConfigSync(markdownlintJsonPath, [ JSON.parse ]));
// The following call is valid, but disallowed because TypeScript does not markdownlint.readConfig(markdownlintJsonPath, assertConfigurationCallback);
// currently propagate {ConfigurationParser[] | null} into the .d.ts file.
// markdownlint.readConfig(markdownlintJsonPath, null, assertConfigCallback);
markdownlint.readConfig(markdownlintJsonPath, [ JSON.parse ], assertConfigurationCallback); markdownlint.readConfig(markdownlintJsonPath, [ JSON.parse ], assertConfigurationCallback);
(async () => {
assertConfigurationCallback(null, await markdownlint.promises.readConfig(markdownlintJsonPath));
assertConfigurationCallback(null, await markdownlint.promises.readConfig(markdownlintJsonPath, [ JSON.parse ]))
})();
let options: markdownlint.Options; let options: markdownlint.Options;
options = { options = {
"files": [ "../bad.md" ], "files": [ "../bad.md" ],
@ -91,10 +94,16 @@ options = {
assertLintResults(markdownlint.sync(options)); assertLintResults(markdownlint.sync(options));
markdownlint(options, assertLintResultsCallback); markdownlint(options, assertLintResultsCallback);
(async () => {
assertLintResultsCallback(null, await markdownlint.promises.markdownlint(options));
})();
options.files = "../bad.md"; options.files = "../bad.md";
assertLintResults(markdownlint.sync(options)); assertLintResults(markdownlint.sync(options));
markdownlint(options, assertLintResultsCallback); markdownlint(options, assertLintResultsCallback);
(async () => {
assertLintResultsCallback(null, await markdownlint.promises.markdownlint(options));
})();
const testRule = { const testRule = {
"names": [ "test-rule" ], "names": [ "test-rule" ],
@ -139,3 +148,6 @@ const testRule = {
options.customRules = [ testRule ]; options.customRules = [ testRule ];
assertLintResults(markdownlint.sync(options)); assertLintResults(markdownlint.sync(options));
markdownlint(options, assertLintResultsCallback); markdownlint(options, assertLintResultsCallback);
(async () => {
assertLintResultsCallback(null, await markdownlint.promises.markdownlint(options));
})();

51
lib/markdownlint.d.ts vendored
View file

@ -8,7 +8,7 @@ export = markdownlint;
*/ */
declare function markdownlint(options: Options, callback: LintCallback): void; declare function markdownlint(options: Options, callback: LintCallback): void;
declare namespace markdownlint { declare namespace markdownlint {
export { markdownlintSync as sync, readConfig, readConfigSync, RuleFunction, RuleParams, MarkdownItToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback }; export { markdownlintSync as sync, readConfig, readConfigSync, promises, RuleFunction, RuleParams, MarkdownItToken, RuleOnError, RuleOnErrorInfo, RuleOnErrorFixInfo, Rule, Options, Plugin, ToStringCallback, LintResults, LintError, FixInfo, LintCallback, Configuration, RuleConfiguration, ConfigurationParser, ReadConfigCallback };
} }
/** /**
* Configuration options. * Configuration options.
@ -17,7 +17,7 @@ type Options = {
/** /**
* Files to lint. * Files to lint.
*/ */
files?: string | string[]; files?: string[] | string;
/** /**
* Strings to lint. * Strings to lint.
*/ */
@ -27,13 +27,11 @@ type Options = {
/** /**
* Configuration object. * Configuration object.
*/ */
config?: { config?: Configuration;
[x: string]: any;
};
/** /**
* Custom rules. * Custom rules.
*/ */
customRules?: Rule | Rule[]; customRules?: Rule[] | Rule;
/** /**
* Front matter pattern. * Front matter pattern.
*/ */
@ -58,18 +56,14 @@ type Options = {
/** /**
* Called with the result of the lint operation. * Called with the result of the lint operation.
*/ */
type LintCallback = (err: Error, results?: { type LintCallback = (err: Error | null, results?: LintResults) => void;
[x: string]: LintError[];
}) => void;
/** /**
* Lint specified Markdown files synchronously. * Lint specified Markdown files synchronously.
* *
* @param {Options} options Configuration options. * @param {Options} options Configuration options.
* @returns {LintResults} Results object. * @returns {LintResults} Results object.
*/ */
declare function markdownlintSync(options: Options): { declare function markdownlintSync(options: Options): LintResults;
[x: string]: LintError[];
};
/** /**
* Read specified configuration file. * Read specified configuration file.
* *
@ -87,9 +81,11 @@ declare function readConfig(file: string, parsers: ConfigurationParser[] | ReadC
* @param {ConfigurationParser[]} [parsers] Parsing function(s). * @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @returns {Configuration} Configuration object. * @returns {Configuration} Configuration object.
*/ */
declare function readConfigSync(file: string, parsers?: ConfigurationParser[]): { declare function readConfigSync(file: string, parsers?: ConfigurationParser[]): Configuration;
[x: string]: any; declare namespace promises {
}; export { markdownlintPromise as markdownlint };
export { readConfigPromise as readConfig };
}
/** /**
* Function to implement rule logic. * Function to implement rule logic.
*/ */
@ -117,7 +113,7 @@ type RuleParams = {
/** /**
* Rule configuration. * Rule configuration.
*/ */
config: any; config: RuleConfiguration;
}; };
/** /**
* Markdown-It token. * Markdown-It token.
@ -341,12 +337,23 @@ type RuleConfiguration = any;
/** /**
* Parses a configuration string and returns a configuration object. * Parses a configuration string and returns a configuration object.
*/ */
type ConfigurationParser = (text: string) => { type ConfigurationParser = (text: string) => Configuration;
[x: string]: any;
};
/** /**
* Called with the result of the readConfig operation. * Called with the result of the readConfig operation.
*/ */
type ReadConfigCallback = (err: Error, config?: { type ReadConfigCallback = (err: Error | null, config?: Configuration) => void;
[x: string]: any; /**
}) => void; * Lint specified Markdown files.
*
* @param {Options} options Configuration options.
* @returns {Promise<LintResults>} Results object.
*/
declare function markdownlintPromise(options: Options): Promise<LintResults>;
/**
* Read specified configuration file.
*
* @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @returns {Promise<Configuration>} Configuration object.
*/
declare function readConfigPromise(file: string, parsers?: ConfigurationParser[]): Promise<Configuration>;

View file

@ -4,6 +4,7 @@
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const util = require("util");
const markdownIt = require("markdown-it"); const markdownIt = require("markdown-it");
const rules = require("./rules"); const rules = require("./rules");
const helpers = require("../helpers"); const helpers = require("../helpers");
@ -11,7 +12,6 @@ const cache = require("./cache");
const deprecatedRuleNames = [ "MD002", "MD006" ]; const deprecatedRuleNames = [ "MD002", "MD006" ];
/** /**
* Validate the list of rules for structure and reuse. * Validate the list of rules for structure and reuse.
* *
@ -834,6 +834,18 @@ function markdownlint(options, callback) {
return lintInput(options, false, callback); return lintInput(options, false, callback);
} }
const markdownlintPromisify = util.promisify(markdownlint);
/**
* Lint specified Markdown files.
*
* @param {Options} options Configuration options.
* @returns {Promise<LintResults>} Results object.
*/
function markdownlintPromise(options) {
return markdownlintPromisify(options);
}
/** /**
* Lint specified Markdown files synchronously. * Lint specified Markdown files synchronously.
* *
@ -928,6 +940,20 @@ function readConfig(file, parsers, callback) {
}); });
} }
const readConfigPromisify = util.promisify(readConfig);
/**
* Read specified configuration file.
*
* @param {string} file Configuration file name.
* @param {ConfigurationParser[]} [parsers] Parsing function(s).
* @returns {Promise<Configuration>} Configuration object.
*/
function readConfigPromise(file, parsers) {
// @ts-ignore
return readConfigPromisify(file, parsers);
}
/** /**
* Read specified configuration file synchronously. * Read specified configuration file synchronously.
* *
@ -959,10 +985,14 @@ function readConfigSync(file, parsers) {
return config; return config;
} }
// Export a/synchronous APIs // Export a/synchronous/Promise APIs
markdownlint.sync = markdownlintSync; markdownlint.sync = markdownlintSync;
markdownlint.readConfig = readConfig; markdownlint.readConfig = readConfig;
markdownlint.readConfigSync = readConfigSync; markdownlint.readConfigSync = readConfigSync;
markdownlint.promises = {
"markdownlint": markdownlintPromise,
"readConfig": readConfigPromise
};
module.exports = markdownlint; module.exports = markdownlint;
// Type declarations // Type declarations
@ -1081,11 +1111,8 @@ module.exports = markdownlint;
* Lint results (for resultVersion 3). * Lint results (for resultVersion 3).
* *
* @typedef {Object.<string, LintError[]>} LintResults * @typedef {Object.<string, LintError[]>} LintResults
* @property {ToStringCallback} toString String representation.
*/ */
// The following should be part of the LintResults typedef, but that causes
// TypeScript to "forget" about the string map.
// * @property {ToStringCallback} toString String representation.
// https://github.com/microsoft/TypeScript/issues/34911
/** /**
* Lint error. * Lint error.

View file

@ -23,6 +23,51 @@ const version = packageJson.version;
const deprecatedRuleNames = new Set([ "MD002", "MD006" ]); const deprecatedRuleNames = new Set([ "MD002", "MD006" ]);
tape("simpleAsync", (test) => {
test.plan(2);
const options = {
"strings": {
"content": "# Heading"
}
};
const expected = "content: 1: MD047/single-trailing-newline " +
"Files should end with a single newline character";
markdownlint(options, (err, actual) => {
test.ifError(err);
test.equal(actual.toString(), expected, "Unexpected results.");
test.end();
});
});
tape("simpleSync", (test) => {
test.plan(1);
const options = {
"strings": {
"content": "# Heading"
}
};
const expected = "content: 1: MD047/single-trailing-newline " +
"Files should end with a single newline character";
const actual = markdownlint.sync(options).toString();
test.equal(actual, expected, "Unexpected results.");
test.end();
});
tape("simplePromise", (test) => {
test.plan(1);
const options = {
"strings": {
"content": "# Heading"
}
};
const expected = "content: 1: MD047/single-trailing-newline " +
"Files should end with a single newline character";
markdownlint.promises.markdownlint(options).then((actual) => {
test.equal(actual.toString(), expected, "Unexpected results.");
test.end();
});
});
tape("projectFilesNoInlineConfig", (test) => { tape("projectFilesNoInlineConfig", (test) => {
test.plan(2); test.plan(2);
const options = { const options = {
@ -698,6 +743,21 @@ tape("badFileSync", (test) => {
test.end(); test.end();
}); });
tape("badFilePromise", (test) => {
test.plan(3);
markdownlint.promises.markdownlint({
"files": [ "./badFile" ]
}).then(
null,
(error) => {
test.ok(error, "Did not get an error for bad file.");
test.ok(error instanceof Error, "Error not instance of Error.");
test.equal(error.code, "ENOENT", "Error code for bad file not ENOENT.");
test.end();
}
);
});
tape("missingStringValue", (test) => { tape("missingStringValue", (test) => {
test.plan(2); test.plan(2);
markdownlint({ markdownlint({
@ -1197,6 +1257,29 @@ tape("configBadHybridSync", (test) => {
test.end(); test.end();
}); });
tape("configSinglePromise", (test) => {
test.plan(1);
markdownlint.promises.readConfig("./test/config/config-child.json")
.then((actual) => {
const expected = require("./config/config-child.json");
test.deepEqual(actual, expected, "Config object not correct.");
test.end();
});
});
tape("configBadFilePromise", (test) => {
test.plan(2);
markdownlint.promises.readConfig("./test/config/config-badfile.json")
.then(
null,
(error) => {
test.ok(error, "Did not get an error for bad JSON.");
test.ok(error instanceof Error, "Error not instance of Error.");
test.end();
}
);
});
tape("allBuiltInRulesHaveValidUrl", (test) => { tape("allBuiltInRulesHaveValidUrl", (test) => {
test.plan(132); test.plan(132);
rules.forEach(function forRule(rule) { rules.forEach(function forRule(rule) {