diff --git a/doc/configure.md b/doc/configure.md index fc9fd19..3367e9c 100644 --- a/doc/configure.md +++ b/doc/configure.md @@ -10,7 +10,7 @@ through configuration files. * If [`packageField`][package-field] is given, `package.json` (JSON) files are loaded and their `$packageField`s are used as configuration. -* One configuration file (JSON) can be given through [`rcPath`][rc-path], +* One configuration file can be given through [`rcPath`][rc-path], this is loaded regardless of `detectConfig` and `rcName`. ###### Example @@ -20,33 +20,26 @@ An example **rc** file could look as follows: ```json { "output": true, + "presets": ["lint-recommended"], "settings": { "bullet": "*", "ruleRepetition": 3, "fences": true }, - "plugins": { - "inline-links": null, - "lint": { - "external": [ - "remark-lint-no-empty-sections" - ], - "maximum-line-length": false - } - } + "plugins": ["inline-links"] } ``` ###### Example -An example **rc.js** file could look as follows: - Scripts expose either an object, or a function which when invoked returns an object. The latter is given the current configuration for a file. This configuration always has a `plugins` object where the keys are resolved absolute paths to plugins, where the values are objects or `false`. +An example **rc.js** file could look as follows: + ```js /** * @fileoverview Local remark configuration. @@ -54,15 +47,12 @@ objects or `false`. module.exports = { output: true, - plugins: { + preset: 'lint-recommended', + plugins: [ /* Custom natural-language validation. */ - 'script/natural-language': null, - 'lint': { - /* Ignore `final-definition` for `license` */ - 'final-definition': false - }, - 'license': null - }, + 'script/natural-language', + 'license' + ], settings: { /* I personally like asterisks. */ bullet: '*' @@ -200,6 +190,49 @@ configures the parser and compiler of the processor. * Type: `Object`. +###### `presets` + +The `presets` field has either a list of preset names (or paths) or an +object mapping presets to their options. It’s also possible to pass +one preset by passing a `string`. + +Presets are in fact configuration files as well: go ahead and publish +your configuration files, if they’re in JSON or JS, on npm. + +Accepts a string: + +```json +{ + "presets": "foo" +} +``` + +Accepts an array: + +```json +{ + "presets": [ + "foo", + "bar" + ] +} +``` + +Or an object: + +```json +{ + "presets": { + "foo": null, + "bar": { + "baz": "qux" + } + } +} +``` + +* Type: `string`, `Array.` or `Object.`. + ###### `plugins` The `plugins` field, related to [`plugins`][plugins] in `options`, has diff --git a/doc/options.md b/doc/options.md index 96ec478..d80ec1a 100644 --- a/doc/options.md +++ b/doc/options.md @@ -29,6 +29,7 @@ authors. * [options.silentlyIgnore](#optionssilentlyignore) * [options.plugins](#optionsplugins) * [options.pluginPrefix](#optionspluginprefix) +* [options.presetPrefix](#optionspresetprefix) * [options.injectedPlugins](#optionsinjectedplugins) * [options.color](#optionscolor) * [options.silent](#optionssilent) @@ -327,10 +328,10 @@ be set from configuration files. * When `true`, overwrites the given files; * When pointing to an existing directory, files are written - to that directory and keep their filenames and extensions; + to that directory and keep their basename; * When the parent directory of the given path exists and one file is processed, the file is written to the given path - using the given filename (and optionally extension); + using the given basename; * Otherwise, a fatal error is thrown. @@ -831,6 +832,46 @@ engine({ }); ``` +## `options.presetPrefix` + +Allow presets to be loaded from configuration files without their +prefix. + +* Type: `string` or `false`, optional. + +###### Example + +The following example processes `readme.md` and loads the +`lint-recommended` preset (from a `package.json`). +Because `presetPrefix` is given, this resolves to +`remark-preset-lint-recommended`. + +```js +var engine = require('unified-engine'); +var remark = require('remark'); + +engine({ + processor: remark(), + globs: ['readme.md'], + packageField: 'remarkConfig', + presetPrefix: 'remark-preset' +}, function (err) { + if (err) throw err; +}); +``` + +Where `package.json` contains: + +```json +{ + "name": "foo", + "private": true, + "remarkConfig": { + "presets": "lint-recommended" + } +} +``` + ## `options.injectedPlugins` Already loaded plug-ins to attach with their options to the processor diff --git a/doc/plug-ins.md b/doc/plug-ins.md index cab58f5..e09949b 100644 --- a/doc/plug-ins.md +++ b/doc/plug-ins.md @@ -24,7 +24,7 @@ var remark = require('remark'); function plugin(processor, options, set) { function completer(set) { console.log('done:', set.valueOf().map(function (file) { - return file.filePath(); + return file.path; })); } diff --git a/lib/configuration.js b/lib/configuration.js index 8048116..cb7852c 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -8,13 +8,15 @@ 'use strict'; +/* eslint-disable max-params */ + /* Dependencies. */ var fs = require('fs'); var path = require('path'); var debug = require('debug')('unified-engine:configuration'); var home = require('user-home'); var yaml = require('js-yaml'); -var resolvePlugin = require('load-plugin').resolve; +var resolve = require('load-plugin').resolve; var findUp = require('vfile-find-up'); var json = require('parse-json'); @@ -24,32 +26,31 @@ module.exports = Configuration; /* Methods. */ var read = fs.readFileSync; var exists = fs.existsSync; -var resolve = path.resolve; var dirname = path.dirname; +var basename = path.basename; var extname = path.extname; var concat = [].concat; /* Constants. */ -var PACKAGE_NAME = 'package'; -var PACKAGE_EXTENSION = 'json'; -var PACKAGE_FILENAME = [PACKAGE_NAME, PACKAGE_EXTENSION].join('.'); -var SCRIPT_EXTENSION = 'js'; -var YAML_EXTENSION = 'yaml'; +var PACKAGE_BASENAME = 'package.json'; +var SCRIPT_EXTNAME = '.js'; +var YAML_EXTNAME = '.yaml'; var PLUGIN_KEY = 'plugins'; +var PRESET_KEY = 'presets'; /** * Merge two configurations, `configuration` into * `target`. * - * @param {Object} target - Configuration to merge into. - * @param {Object} configuration - Configuration to merge - * from. - * @param {string} [source] - Used internally no map - * changes to source files, and to ensure the plug-in - * key is only escaped at root-level. + * @private + * @param {Object} target - Config to merge into. + * @param {Object} configuration - Config to merge from. + * @param {Object} settings - How to merge. * @return {Object} - `target`. */ -function merge(target, configuration, resolveConfig, source) { +function merge(target, configuration, settings, source, options) { + var pluginOptions = {cwd: settings.cwd, prefix: settings.pluginPrefix}; + var presetOptions = {cwd: settings.cwd, prefix: settings.presetPrefix}; var key; var value; var index; @@ -60,15 +61,37 @@ function merge(target, configuration, resolveConfig, source) { var subvalue; if (typeof configuration === 'function') { - configuration = configuration(target); + configuration = configuration(target, options); } - /* Walk `configuration`. */ - for (key in configuration) { - value = configuration[key]; - result = target[key]; + if (source) { + /* Do `presets` first. */ + if (configuration[PRESET_KEY]) { + value = configuration[PRESET_KEY]; + + if (typeof value === 'string') { + value = [value]; + } + + if ('length' in value) { + length = value.length; + index = -1; + + while (++index < length) { + preset(value[index]); + } + } else { + for (name in value) { + preset(name, value[name]); + } + } + } + + /* Now do plug-ins. */ + if (configuration[PLUGIN_KEY]) { + value = configuration[PLUGIN_KEY]; + result = target[PLUGIN_KEY]; - if (key === PLUGIN_KEY && source) { if (!result) { target[PLUGIN_KEY] = result = {}; } @@ -79,7 +102,7 @@ function merge(target, configuration, resolveConfig, source) { while (++index < length) { name = value[index]; - plugin = resolvePlugin(name, resolveConfig) || name; + plugin = resolve(name, pluginOptions) || name; if (!(plugin in result)) { log('Configuring plug-in', plugin, {}); @@ -89,29 +112,41 @@ function merge(target, configuration, resolveConfig, source) { } else { for (name in value) { subvalue = value[name]; - plugin = resolvePlugin(name, resolveConfig) || name; + plugin = resolve(name, pluginOptions) || name; if (subvalue === false) { result[plugin] = false; log('Turning off plug-in', plugin); } else if (!filled(result[plugin])) { - result[plugin] = merge({}, subvalue || {}, resolveConfig); + result[plugin] = merge({}, subvalue || {}, settings); log('Configuring plug-in', plugin, result[plugin]); } else if (filled(subvalue)) { - result[plugin] = merge(result[plugin], subvalue, resolveConfig); + result[plugin] = merge(result[plugin], subvalue, settings); log('Reconfiguring plug-in', plugin, result[plugin]); } else { log('Not reconfiguring plug-in', plugin); } } } - } else if (value && typeof value === 'object') { + } + } + + /* Walk `configuration`. */ + for (key in configuration) { + if (source && (key === PRESET_KEY || key === PLUGIN_KEY)) { + continue; + } + + value = configuration[key]; + result = target[key]; + + if (value && typeof value === 'object') { if ('length' in value) { target[key] = concat.apply(value); log('Setting', key, target[key]); } else if (filled(value)) { if (result) { - target[key] = merge(result, value, resolveConfig); + target[key] = merge(result, value, settings); log('Merging', key, target[key]); } else { target[key] = value; @@ -126,6 +161,17 @@ function merge(target, configuration, resolveConfig, source) { return target; + /* Load a preset. */ + function preset(name, value) { + result = resolve(name, presetOptions) || name; + + log('Loading preset', result, {}); + required(target, result, settings, value); + + debug('Plugins can now load from `%s`', dirname(result)); + settings.cwd.push(dirname(result)); + } + /** * Log a message about setting a value. * @@ -153,21 +199,25 @@ function merge(target, configuration, resolveConfig, source) { * @param {string} filePath - File location. * @return {Object} - Parsed JSON. */ -function load(filePath) { +function load(filePath, packageField) { var configuration = {}; - var extension = extname(filePath).slice(1); + var ext = extname(filePath); var doc; try { - if (extension === SCRIPT_EXTENSION) { + if (ext === SCRIPT_EXTNAME) { configuration = require(filePath); } else { doc = read(filePath, 'utf8'); - if (extension === YAML_EXTENSION) { + if (ext === YAML_EXTNAME) { configuration = yaml.safeLoad(doc); } else { configuration = json(doc); + + if (basename(filePath) === PACKAGE_BASENAME) { + configuration = configuration[packageField] || {}; + } } } } catch (err) { @@ -185,33 +235,34 @@ function load(filePath) { * Loads `rcName` and `rcName.js`. * * @param {Object} config - Config to load into. - * @param {string?} rcName - Name of configuration file. - * @return {Object} - Parsed JSON. + * @param {Object} options - Load configuration. */ -function loadUserConfiguration(config, rcName, resolveConfig) { - var configuration = {}; - - /** - * Load one file-path. - * - * @param {string} filePath - Location of config file. - */ - function loadOne(filePath) { - /* istanbul ignore next - not really testable - * as this loads files outside this project. */ - if (exists(filePath)) { - merge(configuration, load(filePath), resolveConfig, filePath); - } - } +function loadUserConfiguration(config, options) { + var name = options.rcName; /* istanbul ignore next - not really testable. */ if (home) { - loadOne(path.join(home, rcName)); - loadOne(path.join(home, [rcName, SCRIPT_EXTENSION].join('.'))); - loadOne(path.join(home, [rcName, YAML_EXTENSION].join('.'))); + optional(config, path.join(home, name), options); + optional(config, path.join(home, [name, SCRIPT_EXTNAME].join('')), options); + optional(config, path.join(home, [name, YAML_EXTNAME].join('')), options); } +} - return configuration; +function optional(config, filePath, settings) { + /* istanbul ignore if - not really testable + * as this loads files outside this project. */ + if (exists(filePath)) { + required(config, filePath, settings); + } +} + +function required(config, filePath, settings, options) { + merge(config, load(filePath, settings.packageField), { + packageField: settings.packageField, + pluginPrefix: settings.pluginPrefix, + presetPrefix: settings.presetPrefix, + cwd: settings.cwd.concat(dirname(filePath)) + }, filePath, options); } /** @@ -220,30 +271,31 @@ function loadUserConfiguration(config, rcName, resolveConfig) { * If no configuration was found by walking upwards, the * current user's config (at `~`) is used. * - * @param {Configuration} context - Configuration object to use. * @param {string} directory - Location to search. + * @param {Object} options - Load options. * @param {Function} callback - Invoked with `files`. */ -function getLocalConfiguration(context, directory, resolveConfig, callback) { - var rcName = context.settings.rcName; - var packageField = context.settings.packageField; +function getLocalConfiguration(directory, options, callback) { + var rcName = options.rcName; + var packageField = options.packageField; var search = []; if (rcName) { search.push( rcName, - [rcName, SCRIPT_EXTENSION].join('.'), - [rcName, YAML_EXTENSION].join('.') + [rcName, SCRIPT_EXTNAME].join(''), + [rcName, YAML_EXTNAME].join('') ); + debug('Looking for `%s` configuration files', search); } if (packageField) { - search.push(PACKAGE_FILENAME); + search.push(PACKAGE_BASENAME); debug('Looking for `%s` fields in `package.json` files', packageField); } - if (!search.length || !context.settings.detectConfig) { + if (!search.length || !options.detectConfig) { debug('Not looking for configuration files'); return callback(null, {}); } @@ -251,41 +303,19 @@ function getLocalConfiguration(context, directory, resolveConfig, callback) { findUp.all(search, directory, function (err, files) { var configuration = {}; var index = files && files.length; - var file; - var local; - var found; while (index--) { - file = files[index]; - try { - local = load(file.filePath()); + required(configuration, files[index].path, options); } catch (err) { return callback(err); } - - if ( - file.filename === PACKAGE_NAME && - file.extension === PACKAGE_EXTENSION - ) { - if (packageField in local) { - local = local[packageField]; - } else { - continue; - } - } - - found = true; - - debug('Using ' + file.filePath()); - - merge(configuration, local, resolveConfig, file.filePath()); } - if (!found) { + if (!filled(configuration)) { debug('Using personal configuration'); - loadUserConfiguration(configuration, rcName, resolveConfig); + loadUserConfiguration(configuration, options); } callback(err, configuration); @@ -300,20 +330,18 @@ function getLocalConfiguration(context, directory, resolveConfig, callback) { * @param {Object} settings - Options to be passed in. */ function Configuration(settings) { - var self = this; var rcPath = settings.rcPath; - var rcFile = {}; - self.settings = settings; - self.cache = {}; + this.settings = settings; + this.cache = {}; if (rcPath) { - debug('Using command line configuration `' + rcPath + '`'); + rcPath = path.resolve(settings.cwd, rcPath); + this.rcPath = rcPath; - rcFile = load(resolve(settings.cwd, rcPath)); + /* Load it so we can fail early. */ + load(rcPath, settings.packageField); } - - self.rcFile = rcFile; } Configuration.prototype.getConfiguration = getConfiguration; @@ -327,12 +355,16 @@ Configuration.prototype.getConfiguration = getConfiguration; */ function getConfiguration(filePath, callback) { var self = this; - var cwd = self.settings.cwd; - var directory = dirname(resolve(cwd, filePath)); + var settings = self.settings; + var directory = dirname(path.resolve(settings.cwd, filePath || 'noop')); var configuration = self.cache[directory]; - var resolveConfig = { - cwd: cwd, - prefix: self.settings.pluginPrefix + var options = { + rcName: settings.rcName, + packageField: settings.packageField, + detectConfig: settings.detectConfig, + pluginPrefix: settings.pluginPrefix, + presetPrefix: settings.presetPrefix, + cwd: [settings.cwd] }; debug('Constructing configuration for `' + filePath + '`'); @@ -344,21 +376,25 @@ function getConfiguration(filePath, callback) { * @param {Object?} [localConfiguration] - Configuration. */ function handleLocalConfiguration(err, localConfiguration) { + var rcPath = self.rcPath; var current = self.cache[directory]; var config = localConfiguration || {}; - merge(config, self.rcFile, resolveConfig, self.settings.rcPath); + if (rcPath) { + debug('Using command line configuration `' + rcPath + '`'); + required(config, rcPath, options); + } merge(config, { - settings: self.settings.settings, - plugins: self.settings.plugins, - output: self.settings.output - }, resolveConfig, 'settings'); + settings: settings.settings, + plugins: settings.plugins, + output: settings.output + }, options, 'settings'); self.cache[directory] = config; current.forEach(function (callback) { - callback(err, config); + callback(err, err ? null : config); }); } @@ -376,7 +412,7 @@ function getConfiguration(filePath, callback) { } self.cache[directory] = [callback]; - getLocalConfiguration(self, directory, resolveConfig, handleLocalConfiguration); + getLocalConfiguration(directory, options, handleLocalConfiguration); } /** diff --git a/lib/file-pipeline/configure.js b/lib/file-pipeline/configure.js index e5de117..a9c4c94 100644 --- a/lib/file-pipeline/configure.js +++ b/lib/file-pipeline/configure.js @@ -10,6 +10,7 @@ /* Dependencies. */ var debug = require('debug')('unified-engine:file-pipeline:configure'); +var stats = require('vfile-statistics'); var fnName = require('fn-name'); /* Expose. */ @@ -28,11 +29,11 @@ function configure(context, file, fileSet, next) { var config = context.configuration; var processor = context.processor; - if (file.hasFailed()) { + if (stats(file).fatal) { return next(); } - config.getConfiguration(file.filePath(), function (err, options) { + config.getConfiguration(file.path, function (err, options) { var allPlugins = context.injectedPlugins.concat(); var plugins; var option; diff --git a/lib/file-pipeline/copy.js b/lib/file-pipeline/copy.js index eada725..c4394a1 100644 --- a/lib/file-pipeline/copy.js +++ b/lib/file-pipeline/copy.js @@ -18,8 +18,6 @@ module.exports = copy; /* Methods. */ var stat = fs.statSync; -var basename = path.basename; -var extname = path.extname; var dirname = path.dirname; var resolve = path.resolve; var relative = path.relative; @@ -36,11 +34,10 @@ var SEPERATOR = path.sep; */ function copy(context, file, fileSet) { var output = context.output; - var outpath = output; var multi = fileSet.expected > 1; - var currentPath = file.filePath(); + var outpath = output; + var currentPath = file.path; var isDir; - var extension; if (typeof outpath !== 'string') { debug('Not copying'); @@ -66,8 +63,7 @@ function copy(context, file, fileSet) { /* This throws, or the parent exists, which * is a directory, but we should keep the - * filename and extension of the given - * file. */ + * basename of the given file. */ stat(resolve(dirname(outpath))).isDirectory(); isDir = false; } @@ -78,14 +74,13 @@ function copy(context, file, fileSet) { ); } - outpath = relative(context.cwd, outpath); - extension = extname(outpath); + outpath = relative(file.cwd, outpath); - file.move({ - extension: isDir ? '' : extension.slice(1), - filename: isDir ? '' : basename(outpath, extension), - directory: isDir ? outpath : dirname(outpath) - }); + if (isDir) { + file.dirname = outpath; + } else { + file.path = outpath; + } - debug('Copying document from %s to %s', currentPath, file.filePath()); + debug('Copying document from %s to %s', currentPath, file.path); } diff --git a/lib/file-pipeline/file-system.js b/lib/file-pipeline/file-system.js index ce8f3ce..f1da3c8 100644 --- a/lib/file-pipeline/file-system.js +++ b/lib/file-pipeline/file-system.js @@ -37,12 +37,12 @@ function fileSystem(context, file, fileSet, done) { return done(); } - if (!file.namespace('unified-engine').given) { + if (!file.data.unifiedEngineGiven) { debug('Ignoring programmatically added file'); return done(); } - destinationPath = file.filePath(); + destinationPath = file.path; if (!destinationPath) { debug('Ignoring file without output location'); diff --git a/lib/file-pipeline/index.js b/lib/file-pipeline/index.js index d62e055..59fc1e8 100644 --- a/lib/file-pipeline/index.js +++ b/lib/file-pipeline/index.js @@ -8,11 +8,8 @@ 'use strict'; -/* - * Dependencies. - */ - -var ware = require('ware'); +/* Dependencies. */ +var trough = require('trough'); var read = require('./read'); var configure = require('./configure'); var parse = require('./parse'); @@ -23,57 +20,43 @@ var copy = require('./copy'); var stdout = require('./stdout'); var fileSystem = require('./file-system'); +/* Expose: This pipeline ensures each of the pipes + * always runs: even if the read pipe fails, + * queue and write trigger. */ +module.exports = trough() + .use(chunk(trough().use(read).use(configure).use(parse).use(transform))) + .use(chunk(trough().use(queue))) + .use(chunk(trough().use(stringify).use(copy).use(stdout).use(fileSystem))); + /** * Factory to run a pipe. Wraps a pipe to trigger an * error on the `file` in `context`, but still call * `next`. * - * @param {Ware} pipe - Middelware. + * @param {Trough} trough - Middelware. * @return {function(Object, Function)} - Runner. */ -function runFactory(pipe) { - /** - * Run `context` through a bound pipe. - * Always invokes `next` (without error). - * - * @param {Object} context - Context. - * @param {File} file - File. - * @param {FileSet} fileSet - Set. - * @param {function(Error?)} done - Completion handler. - */ +function chunk(pipe) { + return run; + + /** Run the bound bound pipe and handles any errors. */ function run(context, file, fileSet, done) { pipe.run(context, file, fileSet, function (err) { + var messages = file.messages; + var pos; + if (err) { - file.quiet = true; - file.fail(err); + pos = messages.indexOf(err); + + if (pos === -1) { + err = file.message(err); + pos = messages.length - 1; + } + + messages[pos].fatal = true; } done(); }); } - - return run; } - -/* - * Middleware, this ensures each of the four pipes - * always runs (so even if the read pipe fails), - * queue, write, and log trigger. - */ - -var pipe = ware() - .use(runFactory( - ware().use(read).use(configure).use(parse).use(transform) - )) - .use(runFactory( - ware().use(queue) - )) - .use(runFactory( - ware().use(stringify).use(copy).use(stdout).use(fileSystem) - )); - -/* - * Expose. - */ - -module.exports = pipe; diff --git a/lib/file-pipeline/parse.js b/lib/file-pipeline/parse.js index 924c732..a337219 100644 --- a/lib/file-pipeline/parse.js +++ b/lib/file-pipeline/parse.js @@ -10,6 +10,7 @@ /* Dependencies. */ var debug = require('debug')('unified-engine:file-pipeline:parse'); +var stats = require('vfile-statistics'); var json = require('parse-json'); /* Expose. */ @@ -22,32 +23,33 @@ module.exports = parse; * @param {File} file - File. */ function parse(context, file) { - if (file.hasFailed()) { + var message; + + if (stats(file).fatal) { return; } if (context.treeIn) { debug('Not parsing already parsed document'); - /* Add the preferred extension to ensure the file, - * when compiled, is correctly recognized. */ - file.move({extension: context.extensions[0] || null}); - try { context.tree = json(file.toString()); } catch (err) { - err.message = 'Cannot read file as tree: ' + - file.filePath() + '\n' + err.message; + err.message = 'Cannot read file as JSON\n' + err.message; - file.fail(err); + message = file.message(err); + message.fatal = true; } + /* Add the preferred extension to ensure the file, + * when compiled, is correctly recognized. */ + file.extname = '.' + context.extensions[0]; file.contents = ''; return; } - debug('Parsing `%s` with `%j`', file.filePath(), context.settings); + debug('Parsing `%s` with `%j`', file.path, context.settings); context.tree = context.processor.parse(file, context.settings); diff --git a/lib/file-pipeline/queue.js b/lib/file-pipeline/queue.js index 377fd59..da911a4 100644 --- a/lib/file-pipeline/queue.js +++ b/lib/file-pipeline/queue.js @@ -10,6 +10,7 @@ /* Dependencies. */ var debug = require('debug')('unified-engine:file-pipeline:queue'); +var stats = require('vfile-statistics'); /* Expose. */ module.exports = queue; @@ -25,7 +26,7 @@ module.exports = queue; * @param {function(Error?)} done - Completion handler. */ function queue(context, file, fileSet, done) { - var sourcePath = file.namespace('unified-engine').sourcePath; + var origin = file.history[0]; var map = fileSet.complete; var complete = true; @@ -33,14 +34,14 @@ function queue(context, file, fileSet, done) { map = fileSet.complete = {}; } - debug('Queueing `%s`', sourcePath); + debug('Queueing `%s`', origin); - map[sourcePath] = done; + map[origin] = done; fileSet.valueOf().forEach(function (file) { - var key = file.namespace('unified-engine').sourcePath; + var key = file.history[0]; - if (file.hasFailed()) { + if (stats(file).fatal) { return; } @@ -63,8 +64,8 @@ function queue(context, file, fileSet, done) { debug('Flushing: all files can be flushed'); /* Flush. */ - for (sourcePath in map) { - map[sourcePath](err); + for (origin in map) { + map[origin](err); } }); } diff --git a/lib/file-pipeline/read.js b/lib/file-pipeline/read.js index 4de0b65..c58169c 100644 --- a/lib/file-pipeline/read.js +++ b/lib/file-pipeline/read.js @@ -12,6 +12,7 @@ var fs = require('fs'); var path = require('path'); var debug = require('debug')('unified-engine:file-pipeline:read'); +var stats = require('vfile-statistics'); /* Expose. */ module.exports = read; @@ -32,12 +33,12 @@ var ENC = 'utf-8'; * @param {function(Error?)} done - Completion handler. */ function read(context, file, fileSet, done) { - var filePath = file.filePath(); + var filePath = file.path; - if (file.contents || file.namespace('unified-engine').streamIn) { + if (file.contents || file.data.unifiedEngineStreamIn) { debug('Not reading file `%s` with contents', filePath); done(); - } else if (file.hasFailed()) { + } else if (stats(file).fatal) { debug('Not reading failed file `%s`', filePath); done(); } else { diff --git a/lib/file-pipeline/stdout.js b/lib/file-pipeline/stdout.js index 71f2fed..b8cde41 100644 --- a/lib/file-pipeline/stdout.js +++ b/lib/file-pipeline/stdout.js @@ -10,6 +10,7 @@ /* Dependencies. */ var debug = require('debug')('unified-engine:file-pipeline:stdout'); +var stats = require('vfile-statistics'); /* Expose. */ module.exports = stdout; @@ -25,10 +26,10 @@ module.exports = stdout; * @param {function(Error?)} next - Completion handler. */ function stdout(context, file, fileSet, next) { - if (!file.namespace('unified-engine').given) { + if (!file.data.unifiedEngineGiven) { debug('Ignoring programmatically added file'); next(); - } else if (file.hasFailed() || context.output || !context.out) { + } else if (stats(file).fatal || context.output || !context.out) { debug('Ignoring writing to `streamOut`'); next(); } else { diff --git a/lib/file-pipeline/stringify.js b/lib/file-pipeline/stringify.js index d5d3ba4..176f3c8 100644 --- a/lib/file-pipeline/stringify.js +++ b/lib/file-pipeline/stringify.js @@ -10,6 +10,7 @@ /* Dependencies. */ var debug = require('debug')('unified-engine:file-pipeline:stringify'); +var stats = require('vfile-statistics'); /* Expose. */ module.exports = stringify; @@ -26,7 +27,7 @@ function stringify(context, file) { var settings = context.settings; var value; - if (file.hasFailed()) { + if (stats(file).fatal) { debug('Not compiling failed document'); return; } @@ -36,12 +37,12 @@ function stringify(context, file) { return; } - debug('Compiling `%s` with `%j`', file.filePath(), settings); + debug('Compiling `%s` with `%j`', file.path, settings); if (context.treeOut) { /* Add a `json` extension to ensure the file is * correctly seen as JSON. */ - file.move({extension: 'json'}); + file.extname = '.json'; value = JSON.stringify(tree, null, 2); } else { diff --git a/lib/file-pipeline/transform.js b/lib/file-pipeline/transform.js index 4b13d57..c8c9c7c 100644 --- a/lib/file-pipeline/transform.js +++ b/lib/file-pipeline/transform.js @@ -10,6 +10,7 @@ /* Dependencies. */ var debug = require('debug')('unified-engine:file-pipeline:transform'); +var stats = require('vfile-statistics'); /* Expose. */ module.exports = transform; @@ -24,12 +25,12 @@ module.exports = transform; * @param {function(Error?)} done - Completion handler. */ function transform(context, file, fileSet, done) { - if (file.hasFailed()) { + if (stats(file).fatal) { done(); return; } - debug('Transforming document `%s`', file.filePath()); + debug('Transforming document `%s`', file.path); context.processor.run(context.tree, file, function (err, node) { debug('Transformed document (error: %s)', err); diff --git a/lib/file-set-pipeline/configure.js b/lib/file-set-pipeline/configure.js index 7ed4bab..97d3e0c 100644 --- a/lib/file-set-pipeline/configure.js +++ b/lib/file-set-pipeline/configure.js @@ -29,6 +29,7 @@ function configure(context, settings) { settings: settings.settings, plugins: settings.plugins, pluginPrefix: settings.pluginPrefix, + presetPrefix: settings.presetPrefix, output: settings.output, cwd: settings.cwd }); diff --git a/lib/file-set-pipeline/file-system.js b/lib/file-set-pipeline/file-system.js index 22570f0..48cc7e1 100644 --- a/lib/file-set-pipeline/file-system.js +++ b/lib/file-set-pipeline/file-system.js @@ -53,22 +53,23 @@ function fileSystem(context, settings, done) { /* Use injected files. */ if (injected) { files = files.filter(function (file) { - var filePath = file.filePath(); - var ignored = ignore.shouldIgnore(filePath); + var message; - if (ignored && skip) { - return false; - } + file.cwd = settings.cwd; + file.dirname = relative(file.cwd, file.dirname); + file.history = [file.path]; - file.quiet = true; - file.directory = relative(settings.cwd, file.directory); - file.history = [file.filePath()]; - file.namespace('unified-engine').given = true; + if (ignore.check(file.path)) { + if (skip) { + return false; + } - if (ignored) { - file.fail('Cannot process given file: it’s ignored'); + message = file.message('Cannot process given file: it’s ignored'); + message.fatal = true; } + file.data.unifiedEngineGiven = true; + return true; }); } diff --git a/lib/file-set-pipeline/index.js b/lib/file-set-pipeline/index.js index b88be80..602c9e1 100644 --- a/lib/file-set-pipeline/index.js +++ b/lib/file-set-pipeline/index.js @@ -9,7 +9,7 @@ 'use strict'; /* Dependencies. */ -var ware = require('ware'); +var trough = require('trough'); var configure = require('./configure'); var fileSystem = require('./file-system'); var stdin = require('./stdin'); @@ -17,7 +17,7 @@ var transform = require('./transform'); var log = require('./log'); /* Expose. */ -module.exports = ware() +module.exports = trough() .use(configure) .use(fileSystem) .use(stdin) diff --git a/lib/file-set-pipeline/log.js b/lib/file-set-pipeline/log.js index 81d277d..1a2a50b 100644 --- a/lib/file-set-pipeline/log.js +++ b/lib/file-set-pipeline/log.js @@ -42,5 +42,5 @@ function log(context, settings, next) { * @return {boolean} - Whether given by user. */ function given(file) { - return file.namespace('unified-engine').given; + return file.data.unifiedEngineGiven; } diff --git a/lib/file-set-pipeline/stdin.js b/lib/file-set-pipeline/stdin.js index 996efb2..301f40b 100644 --- a/lib/file-set-pipeline/stdin.js +++ b/lib/file-set-pipeline/stdin.js @@ -55,15 +55,14 @@ function stdin(context, settings, done) { debug('Reading from `streamIn`'); streamIn.pipe(concat({encoding: 'string'}, function (value) { - var file = toVFile(settings.filePath || ''); - var space = file.namespace('unified-engine'); + var file = toVFile(settings.filePath || undefined); debug('Read from `streamIn`'); + file.cwd = settings.cwd; file.contents = value; - file.quiet = true; - space.given = true; - space.streamIn = true; + file.data.unifiedEngineGiven = true; + file.data.unifiedEngineStreamIn = true; context.files = [file]; diff --git a/lib/file-set-pipeline/transform.js b/lib/file-set-pipeline/transform.js index 21449fd..55fe5a6 100644 --- a/lib/file-set-pipeline/transform.js +++ b/lib/file-set-pipeline/transform.js @@ -34,6 +34,7 @@ function transform(context, settings, done) { cwd: settings.cwd, extensions: settings.extensions, pluginPrefix: settings.pluginPrefix, + presetPrefix: settings.presetPrefix, injectedPlugins: settings.injectedPlugins, treeIn: settings.treeIn, treeOut: settings.treeOut, @@ -46,7 +47,8 @@ function transform(context, settings, done) { * file. Still, just to ensure things work in * the future, we add an extra check. */ if (err) { - file.fail(err); + err = file.message(err); + err.fatal = true; } fileSet.emit('one', file); diff --git a/lib/file-set.js b/lib/file-set.js index b3dfb1c..e6311f3 100644 --- a/lib/file-set.js +++ b/lib/file-set.js @@ -11,7 +11,7 @@ /* Dependencies. */ var events = require('events'); var inherits = require('util').inherits; -var ware = require('ware'); +var trough = require('trough'); var toVFile = require('to-vfile'); /* Expose. */ @@ -27,12 +27,13 @@ function FileSet() { var self = this; self.files = []; - self.sourcePaths = []; + self.origins = []; self.expected = 0; self.actual = 0; - self.pipeline = ware(); + self.pipeline = trough(); + self.plugins = []; events.init.call(self); @@ -77,16 +78,17 @@ function use(plugin) { var duplicate = false; if (plugin && plugin.pluginId) { - duplicate = pipeline.fns.some(function (fn) { + duplicate = self.plugins.some(function (fn) { return fn.pluginId === plugin.pluginId; }); } - if (!duplicate && pipeline.fns.indexOf(plugin) !== -1) { + if (!duplicate && self.plugins.indexOf(plugin) !== -1) { duplicate = true; } if (!duplicate) { + self.plugins.push(plugin); pipeline.use(plugin); } @@ -108,29 +110,20 @@ function use(plugin) { */ function add(file) { var self = this; - var space; - var sourcePath; + var origin; if (typeof file === 'string') { file = toVFile(file); } - space = file.namespace('unified-engine'); - /* Prevent files from being added multiple times. */ - sourcePath = space.sourcePath || file.filePath(); + origin = file.history[0]; - if (self.sourcePaths.indexOf(sourcePath) !== -1) { + if (self.origins.indexOf(origin) !== -1) { return self; } - space.sourcePath = sourcePath; - self.sourcePaths.push(sourcePath); - - /* Prevent moving files. */ - if (!space.given) { - file.move = locked; - } + self.origins.push(origin); /* Add. */ self.valueOf().push(file); @@ -151,17 +144,6 @@ function add(file) { return self; } -/** - * Locked `VFile#move`. - * - * @this {VFile} - * @memberof {VFile} - * @return {VFile} - Context object. - */ -function locked() { - return this; -} - /** * Utility invoked when a single file has completed it's * pipeline, triggering `done` when all files are complete. diff --git a/lib/finder.js b/lib/finder.js index f73b1d1..3177a37 100644 --- a/lib/finder.js +++ b/lib/finder.js @@ -57,7 +57,7 @@ function find(patterns, callback) { var ignore = self.settings.ignore; var extensions = self.settings.extensions; var cwd = self.settings.cwd; - var hasNegations = ignore.hasNegations(); + var negations = ignore.negations(); var globs = []; var given = []; var failed = []; @@ -70,6 +70,7 @@ function find(patterns, callback) { patterns.forEach(function (pattern) { var file; var stats; + var message; if (magic(pattern)) { globs.push(pattern); @@ -83,10 +84,10 @@ function find(patterns, callback) { oneFileMode = stats.isFile(); } catch (err) { file = toVFile(pattern); - file.quiet = true; oneFileMode = false; - file.fail('No such file or directory'); + message = file.message('No such file or directory'); + message.fatal = true; failed.push(file); } @@ -101,24 +102,26 @@ function find(patterns, callback) { * @param {VFile} file - Virtual file or directory. * @return {*} - Results for `vfile-find-down`. */ - function test(file) { - var filePath = file.filePath(); - var extension = file.extension; + function test(file, stats) { + var filePath = file.path; + var extension = (file.extname || '').slice(1); var isGiven = given.indexOf(filePath) !== -1; - var isFile = stat(filePath).isFile(); + var isFile = stats.isFile(); + var message; /* If the file or directory should be ignored, * is hidden and not negated, skip it. * * If it is both ignored and given, trigger a * warning if not `silentlyIgnore`d. */ - if (ignore.shouldIgnore(filePath)) { + if (ignore.check(filePath)) { if (isGiven) { if (skip) { return findDown.SKIP; } - file.fail('Cannot process specified file: it’s ignored'); + message = file.message('Cannot process specified file: it’s ignored'); + message.fatal = true; return findDown.SKIP | findDown.INCLUDE; } @@ -126,7 +129,7 @@ function find(patterns, callback) { /* If there are negation patterns, directories * are still searched for files as their paths * can be made visible by negation patterns. */ - return hasNegations ? false : findDown.SKIP; + return negations ? false : findDown.SKIP; } /* Do not include non-files. */ @@ -149,21 +152,22 @@ function find(patterns, callback) { findDown.all(test, filePaths, function (err, files) { /* Fix `filePath` in relation to `cwd`. */ files.forEach(function (file) { - file.directory = relative(cwd, file.directory); - file.history = [file.filePath()]; + file.cwd = cwd; + file.dirname = relative(cwd, file.dirname); + file.history = [file.path]; }); files = failed.concat(files); /* Sort alphabetically. */ files.sort(function (left, right) { - return left.filePath() > right.filePath(); + return left.path > right.path; }); /* Mark as given. This allows outputting files, * which can be pretty dangerous, so it’s “hidden”. */ files.forEach(function (file) { - file.namespace('unified-engine').given = true; + file.data.unifiedEngineGiven = true; }); callback(err, files); diff --git a/lib/ignore.js b/lib/ignore.js index 6f083b1..d9d8da3 100644 --- a/lib/ignore.js +++ b/lib/ignore.js @@ -26,11 +26,8 @@ var resolve = path.resolve; /* Constants. */ var NODE_MODULES = 'node_modules'; -var C_BACKSLASH = '\\'; -var C_SLASH = '/'; var C_EXCLAMATION = '!'; var CD = './'; -var EMPTY = ''; /** * Find ignore-patterns. @@ -46,9 +43,10 @@ function Ignore(options) { self.cwd = options.cwd; self.detectIgnore = options.detectIgnore; self.ignoreName = options.ignoreName; - self.ignorePath = ignorePath; if (ignorePath) { + ignorePath = self.ignorePath = resolve(options.cwd, ignorePath); + debug('Using ignore file at `' + ignorePath + '`'); self.ignoreFile = load(resolve(self.cwd, ignorePath)); @@ -56,8 +54,8 @@ function Ignore(options) { } /* Expose methods. */ -Ignore.prototype.shouldIgnore = shouldIgnore; -Ignore.prototype.hasNegations = hasNegations; +Ignore.prototype.check = check; +Ignore.prototype.negations = negations; Ignore.prototype.loadPatterns = loadPatterns; /** @@ -68,14 +66,18 @@ Ignore.prototype.loadPatterns = loadPatterns; * @return {boolean} - Whether `filePath` should be ignored * based on the given `patterns`. */ -function shouldIgnore(filePath) { +function check(filePath) { var self = this; - var normalized = relative(self.cwd, filePath) - .replace(C_BACKSLASH, C_SLASH) - .replace(CD, EMPTY); + var normal = filePath; + + if (self.source) { + normal = relative(path.dirname(self.source), path.resolve(self.cwd, filePath)); + } + + normal = normal.replace(/\\/g, '/'); return self.patterns.reduce(function (ignored, pattern) { - var negated = isNegated(pattern); + var negated = negation(pattern); if (negated) { pattern = pattern.slice(1); @@ -85,7 +87,7 @@ function shouldIgnore(filePath) { pattern = pattern.slice(CD.length); } - return match(normalized, pattern) ? !negated : ignored; + return match(normal, pattern) ? !negated : ignored; }, filePath.split(path.sep).some(hidden)); } /** @@ -93,8 +95,8 @@ function shouldIgnore(filePath) { * * @return {boolean} - Whether negated patterns exist. */ -function hasNegations() { - return this.patterns.some(isNegated); +function negations() { + return this.patterns.some(negation); } /** @@ -112,14 +114,16 @@ function loadPatterns(callback) { * Handle succesful pattern getting. * * @param {Array.?} [results] - Patterns + * @param {string} filePath - Location of ignore file. */ - function done(results) { + function done(results, filePath) { self.patterns = results || []; + self.source = filePath || null; callback(); } if (self.ignoreFile) { - done(self.ignoreFile); + done(self.ignoreFile, self.ignorePath); debug('Using `ignoreFile`: %j', self.patterns); return; } @@ -134,12 +138,13 @@ function loadPatterns(callback) { var result; if (!err && file) { + file = resolve(self.cwd, file.path); try { - result = load(file.filePath()); + result = load(file); } catch (err) { /* Empty */ } } - done(result); + done(result, file); debug('Using ignore patterns: %j', self.patterns); }); } @@ -174,18 +179,18 @@ function applicable(value) { * @param {string} pattern - Pattern to check. * @return {boolean} - Whether `pattern` is negated. */ -function isNegated(pattern) { +function negation(pattern) { return pattern.charAt(0) === C_EXCLAMATION; } /** * Check if `pattern` is a (initially) hidden. * - * @param {string} filename - Filename to check. + * @param {string} basename - basename to check. * @return {boolean} - Whether `pattern` is hidden. */ -function hidden(filename) { - return isHidden(filename) || filename === NODE_MODULES; +function hidden(basename) { + return isHidden(basename) || basename === NODE_MODULES; } /** diff --git a/lib/index.js b/lib/index.js index 4ffd112..2ebee30 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,6 +10,7 @@ /* Dependencies. */ var PassThrough = require('stream').PassThrough; +var statistics = require('vfile-statistics'); var fileSetPipeline = require('./file-set-pipeline'); /* Expose. */ @@ -130,6 +131,9 @@ function run(options, callback) { settings.plugins = options.plugins || {}; settings.injectedPlugins = options.injectedPlugins || []; + /* Presets. */ + settings.presetPrefix = options.presetPrefix || null; + /* Reporting. */ settings.color = options.color || false; settings.silent = options.silent || false; @@ -148,12 +152,8 @@ function run(options, callback) { * @param {Object?} context - Context object. */ function next(err, context) { - var failed = ((context || {}).files || []).some(function (file) { - return file.messages.some(function (message) { - return message.fatal === true || - (message.fatal === false && settings.frail); - }); - }); + var stats = statistics((context || {}).files); + var failed = Boolean(settings.frail ? stats.total : stats.fatal); if (err) { callback(err); diff --git a/package.json b/package.json index 430d9e3..9d9298b 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,16 @@ "globby": "^6.0.0", "is-hidden": "^1.0.1", "js-yaml": "^3.6.1", - "load-plugin": "^1.0.0", + "load-plugin": "^2.0.0", "minimatch": "^3.0.0", "parse-json": "^2.2.0", - "to-vfile": "^1.0.0", + "to-vfile": "^2.0.0", + "trough": "^1.0.0", "user-home": "^2.0.0", - "vfile-find-down": "^1.0.0", - "vfile-find-up": "^1.0.0", - "vfile-reporter": "^2.0.0", - "ware": "^1.3.0" + "vfile-find-down": "^2.0.0", + "vfile-find-up": "^2.0.0", + "vfile-reporter": "^3.0.0", + "vfile-statistics": "^1.0.0" }, "repository": { "type": "git", @@ -51,7 +52,7 @@ "remark-toc": "^3.0.0", "remark-validate-links": "^4.0.0", "tape": "^4.4.0", - "unified": "^4.0.1", + "unified": "^5.0.0", "xo": "^0.16.0" }, "scripts": { @@ -66,7 +67,9 @@ "output": true, "plugins": { "lint": { - "no-missing-blank-lines": {"exceptTightLists": true}, + "no-missing-blank-lines": { + "exceptTightLists": true + }, "list-item-spacing": false, "heading-increment": false, "no-duplicate-headings": false diff --git a/readme.md b/readme.md index a94ac89..e98cae1 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,7 @@ engine({ globs: ['.'], extensions: ['md', 'markdown', 'mkd', 'mkdn', 'mkdown'], pluginPrefix: 'remark', + presetPrefix: 'remark-preset', rcName: '.remarkrc', packageField: 'remarkConfig', ignoreName: '.remarkignore', @@ -116,6 +117,8 @@ when done. — Map of plug-in names or paths and options to use. * [`pluginPrefix`][plugin-prefix] (`string`, optional) — When given, optional prefix to use when searching for plug-ins. +* [`presetPrefix`][preset-prefix] (`string`, optional) + — When given, optional prefix to use when searching for presets. * [`injectedPlugins`][injected-plugins] (`Array`, optional) — List of loaded plug-ins to use. * [`color`][color] (`boolean`, default: `false`) @@ -240,6 +243,8 @@ files work. [plugin-prefix]: doc/options.md#optionspluginprefix +[preset-prefix]: doc/options.md#optionspresetprefix + [plugins]: doc/options.md#optionsplugins [injected-plugins]: doc/options.md#optionsinjectedplugins diff --git a/test/color.js b/test/color.js index 04e4b9f..aab80ad 100644 --- a/test/color.js +++ b/test/color.js @@ -44,8 +44,7 @@ test('color', function (t) { stderr(), [ '\x1b[4m\x1b[31mreadme.md\x1b[39m\x1b[24m', - ' 1:1 \x1b[31merror\x1b[39m No ' + - 'such file or directory', + ' 1:1 \x1b[31merror\x1b[39m No such file or directory', '', '\x1b[31m✖\x1b[39m 1 error', '' diff --git a/test/completers.js b/test/completers.js index 74f22c6..935f06a 100644 --- a/test/completers.js +++ b/test/completers.js @@ -38,7 +38,7 @@ test('completers', function (t) { function testSet(set, nr) { var paths = set.files.map(function (file) { - return file.filePath(); + return file.path; }); st.deepEqual( diff --git a/test/configuration-plugins.js b/test/configuration-plugins.js index 32c6a70..543a7fa 100644 --- a/test/configuration-plugins.js +++ b/test/configuration-plugins.js @@ -73,7 +73,7 @@ test('configuration', function (t) { stderr().split('\n').slice(0, 2).join('\n'), [ 'one.txt', - ' 1:1 error Error: Boom!' + ' 1:1 error Error: Boom!' ].join('\n'), 'should report' ); @@ -100,8 +100,7 @@ test('configuration', function (t) { stderr().split('\n').slice(0, 2).join('\n'), [ 'one.txt', - ' 1:1 error Error: Cannot find ' + - 'module \'missing\'' + ' 1:1 error Error: Cannot find module \'missing\'' ].join('\n'), 'should report' ); @@ -130,9 +129,8 @@ test('configuration', function (t) { stderr().split('\n').slice(0, 2).join('\n'), [ 'one.txt', - ' 1:1 error Error: Loading `' + plugin + '` ' + - 'should give a function, not `[object ' + - 'Object]`' + ' 1:1 error Error: Loading `' + plugin + '` should ' + + 'give a function, not `[object Object]`' ].join('\n'), 'should report' ); @@ -159,7 +157,7 @@ test('configuration', function (t) { stderr().split('\n').slice(0, 2).join('\n'), [ 'one.txt', - ' 1:1 error Error: Missing `required`' + ' 1:1 error Error: Missing `required`' ].join('\n'), 'should report' ); diff --git a/test/configuration-presets.js b/test/configuration-presets.js new file mode 100644 index 0000000..0064af3 --- /dev/null +++ b/test/configuration-presets.js @@ -0,0 +1,231 @@ +/** + * @author Titus Wormer + * @copyright 2016 Titus Wormer + * @license MIT + * @module unified-engine + * @fileoverview Test suite for `unified-engine`. + */ + +'use strict'; + +/* Dependencies. */ +var path = require('path'); +var test = require('tape'); +var noop = require('./util/noop-processor'); +var spy = require('./util/spy'); +var engine = require('..'); + +/* Methods. */ +var join = path.join; + +/* Constants. */ +var fixtures = join(__dirname, 'fixtures'); + +/* Tests. */ +test('configuration-presets', function (t) { + t.plan(7); + + t.test('should fail on missing `presets`', function (st) { + var stderr = spy(); + + st.plan(1); + + engine({ + processor: noop, + cwd: join(fixtures, 'config-presets-missing'), + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + var out = stderr().split('\n').slice(0, 3).join('\n'); + + st.deepEqual( + [err, code, out], + [ + null, + 1, + 'nested/two.txt\n' + + ' 1:1 error Error: Cannot read configuration file: ./preset\n' + + 'ENOENT: no such file or directory, open \'./preset\'' + ], + 'should fail' + ); + }); + }); + + t.test('should fail on invalid `presets`', function (st) { + var root = join(fixtures, 'config-presets-invalid'); + var stderr = spy(); + + st.plan(1); + + engine({ + processor: noop, + cwd: root, + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + var out = stderr().split('\n').slice(0, 3).join('\n'); + + st.deepEqual( + [err, code, out.replace(path.join(root, 'preset.js'), '???')], + [ + null, + 1, + 'nested/two.txt\n' + + ' 1:1 error Error: Cannot read configuration file: ???\n' + + 'invalid' + ], + 'should fail' + ); + }); + }); + + t.test('should supports `presets` as `string`', function (st) { + var stderr = spy(); + + /* More assertions are in loaded plugins. */ + st.plan(3); + + engine({ + processor: noop.use(function (processor) { + processor.t = st; + }), + cwd: join(fixtures, 'config-presets-string'), + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + st.deepEqual( + [err, code, stderr()], + [ + null, + 0, + 'nested/two.txt: no issues found\n' + + 'one.txt: no issues found\n' + ], + 'should succeed' + ); + }); + }); + + t.test('should prefer local plugins', function (st) { + var stderr = spy(); + + /* More assertions are in loaded plugins. */ + st.plan(3); + + engine({ + processor: noop.use(function (processor) { + processor.t = st; + }), + cwd: join(fixtures, 'config-presets-local'), + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + st.deepEqual( + [err, code, stderr()], + [ + null, + 0, + 'nested/two.txt: no issues found\n' + + 'one.txt: no issues found\n' + ], + 'should succeed' + ); + }); + }); + + t.test('should supports `presets` as `Array.`', function (st) { + var stderr = spy(); + + /* More assertions are in loaded plugins. */ + st.plan(3); + + engine({ + processor: noop.use(function (processor) { + processor.t = st; + }), + cwd: join(fixtures, 'config-presets-list'), + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + st.deepEqual( + [err, code, stderr()], + [ + null, + 0, + 'nested/two.txt: no issues found\n' + + 'one.txt: no issues found\n' + ], + 'should succeed' + ); + }); + }); + + t.test('should supports `presets` as `Object` (1)', function (st) { + var stderr = spy(); + + /* More assertions are in loaded plugins. */ + st.plan(3); + + engine({ + processor: noop.use(function (processor) { + processor.t = st; + }), + cwd: join(fixtures, 'config-presets-object'), + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + st.deepEqual( + [err, code, stderr()], + [ + null, + 0, + 'nested/two.txt: no issues found\n' + + 'one.txt: no issues found\n' + ], + 'should succeed' + ); + }); + }); + + t.test('should supports `presets` as `Object` (2)', function (st) { + var stderr = spy(); + + /* More assertions are in loaded plugins. */ + st.plan(3); + + engine({ + processor: noop.use(function (processor) { + processor.t = st; + }), + cwd: join(fixtures, 'config-presets-object-no-func'), + streamError: stderr.stream, + globs: ['.'], + rcName: '.foorc', + extensions: ['txt'] + }, function (err, code) { + st.deepEqual( + [err, code, stderr()], + [ + null, + 0, + 'nested/two.txt: no issues found\n' + + 'one.txt: no issues found\n' + ], + 'should succeed' + ); + }); + }); +}); diff --git a/test/configuration.js b/test/configuration.js index ea90832..a74c356 100644 --- a/test/configuration.js +++ b/test/configuration.js @@ -27,7 +27,7 @@ test('configuration', function (t) { engine({ processor: noop, - cwd: join(fixtures, 'empty'), + cwd: join(fixtures, 'one-file'), globs: ['.'], rcPath: '.foorc', extensions: ['txt'] @@ -77,8 +77,7 @@ test('configuration', function (t) { report, [ 'one.txt', - ' 1:1 error Error: Cannot read ' + - 'configuration file' + ' 1:1 error Error: Cannot read configuration file' ].join('\n'), 'should fail fatally when custom .rc files ' + 'are malformed' @@ -140,8 +139,7 @@ test('configuration', function (t) { report.slice(0, report.lastIndexOf(':')), [ 'one.txt', - ' 1:1 error YAMLException: Cannot read ' + - 'configuration file' + ' 1:1 error YAMLException: Cannot read configuration file' ].join('\n'), 'should fail fatally when custom .rc files ' + 'are malformed' @@ -200,8 +198,8 @@ test('configuration', function (t) { stderr().split('\n').slice(0, 4).join('\n'), [ 'one.txt', - ' 1:1 error JSONError: Cannot ' + - 'read configuration file: ' + join(cwd, 'package.json'), + ' 1:1 error JSONError: Cannot read configuration ' + + 'file: ' + join(cwd, 'package.json'), 'No data, empty input at 1:1', '^' ].join('\n'), diff --git a/test/fixtures/config-plugins-cascade/.foorc b/test/fixtures/config-plugins-cascade/.foorc index f9bf56f..f34ae56 100644 --- a/test/fixtures/config-plugins-cascade/.foorc +++ b/test/fixtures/config-plugins-cascade/.foorc @@ -1,3 +1,3 @@ { - "plugins": ["test", "test2"] + "plugins": ["./test", "./test2"] } diff --git a/test/fixtures/config-plugins-cascade/.foorc.js b/test/fixtures/config-plugins-cascade/.foorc.js index bfc2aa1..80c3e3c 100644 --- a/test/fixtures/config-plugins-cascade/.foorc.js +++ b/test/fixtures/config-plugins-cascade/.foorc.js @@ -1,14 +1,14 @@ module.exports = { missing: undefined, plugins: { - test: { + './test': { script: true, cascade: 1 }, - test3: null, - test4: {}, + './test3': null, + './test4': {}, // Set plug-ins to `false` to turn them off. - test5: false + './test5': false } } diff --git a/test/fixtures/config-plugins-cascade/nested/.foorc b/test/fixtures/config-plugins-cascade/nested/.foorc index 253cf5b..48ce0c8 100644 --- a/test/fixtures/config-plugins-cascade/nested/.foorc +++ b/test/fixtures/config-plugins-cascade/nested/.foorc @@ -1,6 +1,6 @@ { "plugins": [ - "test", - "test2" + "./test", + "./test2" ] } diff --git a/test/fixtures/config-plugins-cascade/nested/.foorc.js b/test/fixtures/config-plugins-cascade/nested/.foorc.js index 1211c9b..3ae8fff 100644 --- a/test/fixtures/config-plugins-cascade/nested/.foorc.js +++ b/test/fixtures/config-plugins-cascade/nested/.foorc.js @@ -4,6 +4,6 @@ var opts = { }; module.exports.plugins = { - test: opts, - test2: opts + './test': opts, + './test2': opts } diff --git a/test/fixtures/config-plugins-cascade/nested/package.json b/test/fixtures/config-plugins-cascade/nested/package.json index 27da077..c9e6a3f 100644 --- a/test/fixtures/config-plugins-cascade/nested/package.json +++ b/test/fixtures/config-plugins-cascade/nested/package.json @@ -1,8 +1,8 @@ { "fooConfig": { "plugins": { - "test": null, - "test2": null + "./test": null, + "./test2": null } } } diff --git a/test/fixtures/config-plugins-cascade/package.json b/test/fixtures/config-plugins-cascade/package.json index c281cfc..411b0da 100644 --- a/test/fixtures/config-plugins-cascade/package.json +++ b/test/fixtures/config-plugins-cascade/package.json @@ -2,7 +2,7 @@ "fooConfig": { "coverage": "quuuux", "plugins": { - "test": { + "./test": { "package": ["foo", "bar", "baz"], "cascade": 2 } diff --git a/test/fixtures/config-presets-invalid/.foorc b/test/fixtures/config-presets-invalid/.foorc new file mode 100644 index 0000000..c0a557a --- /dev/null +++ b/test/fixtures/config-presets-invalid/.foorc @@ -0,0 +1,3 @@ +{ + "presets": "./preset" +} diff --git a/test/fixtures/config-presets-invalid/nested/two.txt b/test/fixtures/config-presets-invalid/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-invalid/one.txt b/test/fixtures/config-presets-invalid/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-invalid/preset.js b/test/fixtures/config-presets-invalid/preset.js new file mode 100644 index 0000000..169d47c --- /dev/null +++ b/test/fixtures/config-presets-invalid/preset.js @@ -0,0 +1 @@ +throw new Error('invalid'); diff --git a/test/fixtures/config-presets-list/.foorc b/test/fixtures/config-presets-list/.foorc new file mode 100644 index 0000000..d565b30 --- /dev/null +++ b/test/fixtures/config-presets-list/.foorc @@ -0,0 +1,12 @@ +{ + "presets": [ + "./three", + "./four" + ], + "plugins": { + "./plugin": { + "bravo": false, + "charlie": true + } + } +} diff --git a/test/fixtures/config-presets-list/four/index.js b/test/fixtures/config-presets-list/four/index.js new file mode 100644 index 0000000..c840bcb --- /dev/null +++ b/test/fixtures/config-presets-list/four/index.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + './plugin': { + charlie: true, + delta: true + } + } +}; diff --git a/test/fixtures/config-presets-list/four/plugin.js b/test/fixtures/config-presets-list/four/plugin.js new file mode 100644 index 0000000..17af15a --- /dev/null +++ b/test/fixtures/config-presets-list/four/plugin.js @@ -0,0 +1,3 @@ +module.exports = function () { + throw new Error('Shouldn’t run (the `./plugin` in `three` is used)'); +}; diff --git a/test/fixtures/config-presets-list/nested/two.txt b/test/fixtures/config-presets-list/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-list/one.txt b/test/fixtures/config-presets-list/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-list/three/index.js b/test/fixtures/config-presets-list/three/index.js new file mode 100644 index 0000000..e03712f --- /dev/null +++ b/test/fixtures/config-presets-list/three/index.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + './plugin': { + alpha: true, + bravo: true + } + } +}; diff --git a/test/fixtures/config-presets-list/three/plugin.js b/test/fixtures/config-presets-list/three/plugin.js new file mode 100644 index 0000000..52acdab --- /dev/null +++ b/test/fixtures/config-presets-list/three/plugin.js @@ -0,0 +1,12 @@ +module.exports = function (processor, options) { + processor.t.deepEqual( + options, + { + alpha: true, + bravo: false, + charlie: true, + delta: true + }, + 'should pass the correct options to the preset plugin' + ); +}; diff --git a/test/fixtures/config-presets-local/.foorc b/test/fixtures/config-presets-local/.foorc new file mode 100644 index 0000000..2c0f99e --- /dev/null +++ b/test/fixtures/config-presets-local/.foorc @@ -0,0 +1,9 @@ +{ + "presets": "./preset", + "plugins": { + "./plugin": { + "two": false, + "three": true + } + } +} diff --git a/test/fixtures/config-presets-local/nested/two.txt b/test/fixtures/config-presets-local/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-local/one.txt b/test/fixtures/config-presets-local/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-local/plugin.js b/test/fixtures/config-presets-local/plugin.js new file mode 100644 index 0000000..b9369cb --- /dev/null +++ b/test/fixtures/config-presets-local/plugin.js @@ -0,0 +1,11 @@ +module.exports = function (processor, options) { + processor.t.deepEqual( + options, + { + one: true, + two: false, + three: true + }, + 'should pass the correct options to the local plugin' + ); +}; diff --git a/test/fixtures/config-presets-local/preset/index.js b/test/fixtures/config-presets-local/preset/index.js new file mode 100644 index 0000000..48500df --- /dev/null +++ b/test/fixtures/config-presets-local/preset/index.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + './plugin': { + one: true, + two: true + } + } +}; diff --git a/test/fixtures/config-presets-local/preset/plugin.js b/test/fixtures/config-presets-local/preset/plugin.js new file mode 100644 index 0000000..ba34f3e --- /dev/null +++ b/test/fixtures/config-presets-local/preset/plugin.js @@ -0,0 +1 @@ +throw new Error('Shouldn’t load: local plugins should be preferred'); diff --git a/test/fixtures/config-presets-missing/.foorc b/test/fixtures/config-presets-missing/.foorc new file mode 100644 index 0000000..2c0f99e --- /dev/null +++ b/test/fixtures/config-presets-missing/.foorc @@ -0,0 +1,9 @@ +{ + "presets": "./preset", + "plugins": { + "./plugin": { + "two": false, + "three": true + } + } +} diff --git a/test/fixtures/config-presets-missing/nested/two.txt b/test/fixtures/config-presets-missing/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-missing/one.txt b/test/fixtures/config-presets-missing/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-object-no-func/.foorc b/test/fixtures/config-presets-object-no-func/.foorc new file mode 100644 index 0000000..1fbf647 --- /dev/null +++ b/test/fixtures/config-presets-object-no-func/.foorc @@ -0,0 +1,11 @@ +{ + "presets": { + "./preset": {"some": "ignored stuff"} + }, + "plugins": { + "./plugin": { + "two": false, + "three": true + } + } +} diff --git a/test/fixtures/config-presets-object-no-func/nested/two.txt b/test/fixtures/config-presets-object-no-func/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-object-no-func/one.txt b/test/fixtures/config-presets-object-no-func/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-object-no-func/preset/index.js b/test/fixtures/config-presets-object-no-func/preset/index.js new file mode 100644 index 0000000..48500df --- /dev/null +++ b/test/fixtures/config-presets-object-no-func/preset/index.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + './plugin': { + one: true, + two: true + } + } +}; diff --git a/test/fixtures/config-presets-object-no-func/preset/plugin.js b/test/fixtures/config-presets-object-no-func/preset/plugin.js new file mode 100644 index 0000000..d136a35 --- /dev/null +++ b/test/fixtures/config-presets-object-no-func/preset/plugin.js @@ -0,0 +1,11 @@ +module.exports = function (processor, options) { + processor.t.deepEqual( + options, + { + one: true, + two: false, + three: true + }, + 'should pass the correct options to the preset plugin' + ); +}; diff --git a/test/fixtures/config-presets-object/.foorc b/test/fixtures/config-presets-object/.foorc new file mode 100644 index 0000000..427bdc3 --- /dev/null +++ b/test/fixtures/config-presets-object/.foorc @@ -0,0 +1,5 @@ +{ + "presets": { + "./preset": {"options": "for", "the": "preset"} + } +} diff --git a/test/fixtures/config-presets-object/nested/two.txt b/test/fixtures/config-presets-object/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-object/one.txt b/test/fixtures/config-presets-object/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-object/preset/index.js b/test/fixtures/config-presets-object/preset/index.js new file mode 100644 index 0000000..00fce8e --- /dev/null +++ b/test/fixtures/config-presets-object/preset/index.js @@ -0,0 +1,7 @@ +module.exports = function (current, options) { + return { + plugins: { + './plugin': options + } + }; +}; diff --git a/test/fixtures/config-presets-object/preset/plugin.js b/test/fixtures/config-presets-object/preset/plugin.js new file mode 100644 index 0000000..d6dd045 --- /dev/null +++ b/test/fixtures/config-presets-object/preset/plugin.js @@ -0,0 +1,10 @@ +module.exports = function (processor, options) { + processor.t.deepEqual( + options, + { + options: 'for', + the: 'preset' + }, + 'should pass the correct options to the preset plugin' + ); +}; diff --git a/test/fixtures/config-presets-string/.foorc b/test/fixtures/config-presets-string/.foorc new file mode 100644 index 0000000..2c0f99e --- /dev/null +++ b/test/fixtures/config-presets-string/.foorc @@ -0,0 +1,9 @@ +{ + "presets": "./preset", + "plugins": { + "./plugin": { + "two": false, + "three": true + } + } +} diff --git a/test/fixtures/config-presets-string/nested/two.txt b/test/fixtures/config-presets-string/nested/two.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-string/one.txt b/test/fixtures/config-presets-string/one.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/config-presets-string/preset/index.js b/test/fixtures/config-presets-string/preset/index.js new file mode 100644 index 0000000..48500df --- /dev/null +++ b/test/fixtures/config-presets-string/preset/index.js @@ -0,0 +1,8 @@ +module.exports = { + plugins: { + './plugin': { + one: true, + two: true + } + } +}; diff --git a/test/fixtures/config-presets-string/preset/plugin.js b/test/fixtures/config-presets-string/preset/plugin.js new file mode 100644 index 0000000..d136a35 --- /dev/null +++ b/test/fixtures/config-presets-string/preset/plugin.js @@ -0,0 +1,11 @@ +module.exports = function (processor, options) { + processor.t.deepEqual( + options, + { + one: true, + two: false, + three: true + }, + 'should pass the correct options to the preset plugin' + ); +}; diff --git a/test/fixtures/malformed-plugin/package.json b/test/fixtures/malformed-plugin/package.json index 99c7928..f7aae99 100644 --- a/test/fixtures/malformed-plugin/package.json +++ b/test/fixtures/malformed-plugin/package.json @@ -1,5 +1,5 @@ { "fooConfig": { - "plugins": ["test"] + "plugins": ["./test"] } } diff --git a/test/fixtures/not-a-plugin/package.json b/test/fixtures/not-a-plugin/package.json index 99c7928..f7aae99 100644 --- a/test/fixtures/not-a-plugin/package.json +++ b/test/fixtures/not-a-plugin/package.json @@ -1,5 +1,5 @@ { "fooConfig": { - "plugins": ["test"] + "plugins": ["./test"] } } diff --git a/test/fixtures/throwing-plugin/package.json b/test/fixtures/throwing-plugin/package.json index 99c7928..f7aae99 100644 --- a/test/fixtures/throwing-plugin/package.json +++ b/test/fixtures/throwing-plugin/package.json @@ -1,5 +1,5 @@ { "fooConfig": { - "plugins": ["test"] + "plugins": ["./test"] } } diff --git a/test/index.js b/test/index.js index ac36a9d..df35940 100644 --- a/test/index.js +++ b/test/index.js @@ -13,6 +13,7 @@ require('./input'); require('./ignore'); require('./configuration'); require('./configuration-plugins'); +require('./configuration-presets'); require('./stdin'); require('./output'); require('./tree'); diff --git a/test/input.js b/test/input.js index d0fc7e0..4dd3c3e 100644 --- a/test/input.js +++ b/test/input.js @@ -80,7 +80,7 @@ test('input', function (t) { stderr(), [ 'readme.md', - ' 1:1 error No such file or directory', + ' 1:1 error No such file or directory', '', '✖ 1 error', '' @@ -255,10 +255,8 @@ test('input', function (t) { stderr(), [ 'one.txt: no issues found', - '', 'nested/two.txt', - ' 1:1 error Cannot process ' + - 'given file: it’s ignored', + ' 1:1 error Cannot process given file: it’s ignored', '', 'nested/three.txt: no issues found', '', @@ -289,10 +287,8 @@ test('input', function (t) { stderr(), [ 'nested/two.txt: no issues found', - '', 'node_modules/ignore-one.txt', - ' 1:1 error Cannot process ' + - 'specified file: it’s ignored', + ' 1:1 error Cannot process specified file: it’s ignored', '', 'one.txt: no issues found', '', diff --git a/test/output.js b/test/output.js index d578377..61e916e 100644 --- a/test/output.js +++ b/test/output.js @@ -317,8 +317,8 @@ test('output', function (t) { /* Change the tree */ tree.value = 'two'; - /* Remove the file-path */ - file.directory = file.filename = file.extension = null; + /* Remove the file-path. */ + file.history = []; }; }), cwd: cwd, @@ -334,7 +334,7 @@ test('output', function (t) { st.equal(input, '', 'should not modify the input'); st.equal( stderr(true), - 'one.txt: no issues found\n', + ': no issues found\n', 'should not report' ); }); @@ -364,8 +364,8 @@ test('output', function (t) { st.equal( report.join('\n'), 'one.txt\n' + - ' 1:1 error Error: Cannot write ' + - 'multiple files to single output', + ' 1:1 error Error: Cannot write multiple files to ' + + 'single output', 'should report' ); }); @@ -397,8 +397,7 @@ test('output', function (t) { st.equal( report.join('\n'), 'one.txt\n' + - ' 1:1 error Error: Cannot read ' + - 'output directory. Error:\n', + ' 1:1 error Error: Cannot read output directory. Error:\n', 'should report' ); }); diff --git a/test/reporting.js b/test/reporting.js index 10f0372..ddc5452 100644 --- a/test/reporting.js +++ b/test/reporting.js @@ -33,7 +33,7 @@ test('reporting', function (t) { engine({ processor: noop().use(function () { return function (tree, file) { - file.warn('Warning'); + file.message('Warning'); }; }), cwd: join(fixtures, 'one-file'), @@ -48,7 +48,7 @@ test('reporting', function (t) { stderr(), [ 'one.txt', - ' 1:1 warning Warning', + ' 1:1 warning Warning', '', '⚠ 1 warning', '' @@ -68,8 +68,8 @@ test('reporting', function (t) { engine({ processor: noop().use(function () { return function (tree, file) { - if (file.filename === 'two') { - file.warn('Warning!'); + if (file.stem === 'two') { + file.message('Warning!'); } }; }), @@ -86,7 +86,7 @@ test('reporting', function (t) { stderr(), [ 'two.txt', - ' 1:1 warning Warning!', + ' 1:1 warning Warning!', '', '⚠ 1 warning', '' @@ -120,8 +120,7 @@ test('reporting', function (t) { ); t.test( - 'should not report succesful files when ' + - '`silent`', + 'should not report succesful files when `silent`', function (st) { var stderr = spy(); @@ -130,9 +129,9 @@ test('reporting', function (t) { engine({ processor: noop().use(function () { return function (tree, file) { - file.warn('Warning!'); + file.message('Warning!'); - if (file.filename === 'two') { + if (file.stem === 'two') { file.fail('Error!'); } }; @@ -150,7 +149,7 @@ test('reporting', function (t) { stderr(), [ 'two.txt', - ' 1:1 error Error!', + ' 1:1 error Error!', '', '✖ 1 error', '' diff --git a/test/tree.js b/test/tree.js index e3af7f6..48e5198 100644 --- a/test/tree.js +++ b/test/tree.js @@ -49,8 +49,7 @@ test('tree', function (t) { stderr(true).split('\n').slice(0, 4).join('\n'), [ 'doc.json', - ' 1:1 error JSONError: Cannot ' + - 'read file as tree: doc.json', + ' 1:1 error JSONError: Cannot read file as JSON', 'No data, empty input at 1:1', '^' ].join('\n'), @@ -197,7 +196,7 @@ test('tree', function (t) { processor: noop, cwd: cwd, streamError: stderr.stream, - output: 'bar', + output: 'bar.json', treeOut: true, files: [ toVFile(join(cwd, 'one.txt'))