diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md index 3a7e17650c1e..631a7203b91a 100644 --- a/docs/user-guide/command-line-interface.md +++ b/docs/user-guide/command-line-interface.md @@ -333,6 +333,17 @@ Example: eslint -f ./customformat.js file.js +An npm-installed formatter is resolved with or without `eslint-formatter-` prefix. + +Example: + + npm install eslint-formatter-pretty + + eslint -f pretty file.js + + // equivalent: + eslint -f eslint-formatter-pretty file.js + When specified, the given format is output to the console. If you'd like to save that output into a file, you can do so on the command line like so: eslint -f compact file.js > results.txt diff --git a/lib/cli-engine.js b/lib/cli-engine.js index 38c49cb31d84..04f1d83f7904 100644 --- a/lib/cli-engine.js +++ b/lib/cli-engine.js @@ -26,10 +26,14 @@ const fs = require("fs"), validator = require("./config/config-validator"), stringify = require("json-stable-stringify"), hash = require("./util/hash"), + ModuleResolver = require("./util/module-resolver"), + naming = require("./util/naming"), pkg = require("../package.json"); const debug = require("debug")("eslint:cli-engine"); +const resolver = new ModuleResolver(); + //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ @@ -670,15 +674,28 @@ class CLIEngine { // replace \ with / for Windows compatibility format = format.replace(/\\/g, "/"); + let cwd = this.options ? this.options.cwd : process.cwd(); + + const namespace = naming.getNamespaceFromTerm(format); + + if (namespace) { + cwd = `${cwd}/${namespace}`; + format = naming.removeNamespaceFromTerm(format); + } + let formatterPath; // if there's a slash, then it's a file if (format.indexOf("/") > -1) { - const cwd = this.options ? this.options.cwd : process.cwd(); - formatterPath = path.resolve(cwd, format); } else { - formatterPath = `./formatters/${format}`; + const npmFormat = naming.addPrefixToTerm("eslint-formatter-", format); + + try { + formatterPath = resolver.resolve(npmFormat, cwd); + } catch (_) { + formatterPath = `./formatters/${format}`; + } } try { diff --git a/lib/config/plugins.js b/lib/config/plugins.js index 9884f360391a..c557c7b44edb 100644 --- a/lib/config/plugins.js +++ b/lib/config/plugins.js @@ -9,13 +9,13 @@ //------------------------------------------------------------------------------ const debug = require("debug")("eslint:plugins"); +const naming = require("../util/naming"); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ -const PLUGIN_NAME_PREFIX = "eslint-plugin-", - NAMESPACE_REGEX = /^@.*\//i; +const PLUGIN_NAME_PREFIX = "eslint-plugin-"; //------------------------------------------------------------------------------ // Public Interface @@ -43,7 +43,7 @@ class Plugins { * @returns {string} The name of the plugin without prefix. */ static removePrefix(pluginName) { - return pluginName.startsWith(PLUGIN_NAME_PREFIX) ? pluginName.slice(PLUGIN_NAME_PREFIX.length) : pluginName; + return naming.removePrefixFromTerm(PLUGIN_NAME_PREFIX, pluginName); } /** @@ -52,7 +52,7 @@ class Plugins { * @returns {string} The name of the plugins namepace if it has one. */ static getNamespace(pluginName) { - return pluginName.match(NAMESPACE_REGEX) ? pluginName.match(NAMESPACE_REGEX)[0] : ""; + return naming.getNamespaceFromTerm(pluginName); } /** @@ -61,7 +61,7 @@ class Plugins { * @returns {string} The name of the plugin without the namespace. */ static removeNamespace(pluginName) { - return pluginName.replace(NAMESPACE_REGEX, ""); + return naming.removeNamespaceFromTerm(pluginName); } /** diff --git a/tests/fixtures/cli-engine/@somenamespace/eslint-formatter-foo/index.js b/tests/fixtures/cli-engine/@somenamespace/eslint-formatter-foo/index.js new file mode 100644 index 000000000000..0a55f349ae71 --- /dev/null +++ b/tests/fixtures/cli-engine/@somenamespace/eslint-formatter-foo/index.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/tests/fixtures/cli-engine/eslint-formatter-bar/index.js b/tests/fixtures/cli-engine/eslint-formatter-bar/index.js new file mode 100644 index 000000000000..0a55f349ae71 --- /dev/null +++ b/tests/fixtures/cli-engine/eslint-formatter-bar/index.js @@ -0,0 +1 @@ +module.exports = function() {}; diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js index 5026d4b197e7..7b17b25d16db 100644 --- a/tests/lib/cli-engine.js +++ b/tests/lib/cli-engine.js @@ -2685,6 +2685,42 @@ describe("CLIEngine", () => { assert.isFunction(formatter); }); + it("should return a function when a formatter prefixed with eslint-formatter is requested", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine") + }), + formatter = engine.getFormatter("bar"); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine") + }), + formatter = engine.getFormatter("eslint-formatter-bar"); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter is requested within a scoped npm package", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine") + }), + formatter = engine.getFormatter("@somenamespace/foo"); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine") + }), + formatter = engine.getFormatter("@somenamespace/eslint-formatter-foo"); + + assert.isFunction(formatter); + }); + it("should return null when a customer formatter doesn't exist", () => { const engine = new CLIEngine(), formatterPath = getFixturePath("formatters", "doesntexist.js");