diff --git a/README.md b/README.md index 93e2b52..c3f3b7a 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,28 @@ You may use `env` as an alias for `envs` (just don't specify both). If you're using your custom linter globally (you installed it with `-g`), then you also need to install `babel-eslint` globally with `npm install babel-eslint -g`. +### Extension whitelist + +`standard-engine` can be set up to look for whitelisted +[ESLint shareable config](http://eslint.org/docs/developer-guide/shareable-configs)s +to extend the rules from. + +For example, an `opts` object may include: + +```js +{ + whitelist: ['pocketlint-node', 'pocketlint-ava'] +} +``` + +Then, if any of the packages `eslint-config-pocketlint-node`/`eslint-config-pocketlint-ava` +are found to be installed, the rules will extend from them, as well. + +This feature allows users of this package to curate a set of official extensions. + +Intentionally, this is not read from `package.json`. If it were, it would not have been +a whitelist feature. + ## API Usage ### `standardEngine.lintText(text, [opts], callback)` @@ -215,6 +237,7 @@ be provided: globals: [], // custom global variables to declare plugins: [], // custom eslint plugins envs: [], // custom eslint environment + whitelist: []// shareable configs to extend from if installed parser: '' // custom js parser (e.g. babel-eslint) } ``` @@ -253,6 +276,7 @@ Lint the provided `files` globs. An `opts` object may be provided: globals: [], // custom global variables to declare plugins: [], // custom eslint plugins envs: [], // custom eslint environment + whitelist: []// shareable configs to extend from if installed parser: '' // custom js parser (e.g. babel-eslint) } ``` @@ -276,6 +300,7 @@ This is the full set of options accepted by the above APIs. Not all options make globals: [], // custom global variables to declare plugins: [], // custom eslint plugins envs: [], // custom eslint environment + whitelist: []// shareable configs to extend from if installed parser: '' // custom js parser (e.g. babel-eslint) } ``` diff --git a/index.js b/index.js index f288754..12b98ae 100644 --- a/index.js +++ b/index.js @@ -41,6 +41,24 @@ function Linter (opts) { plugins: [], useEslintrc: false }, opts.eslintConfig) + + if (opts.whitelist) { + let original = self.eslintConfig.extends + let ekstends = original + ekstends = typeof ekstends === 'string' ? [ekstends] : ekstends || [] + opts.whitelist.forEach((shareable) => { + let installed = true + try { + require('eslint-config-' + shareable) + } catch (e) { + installed = false + } + if (installed) { + ekstends.push(shareable) + } + }) + self.eslintConfig.extends = ekstends.length ? ekstends : original + } } /** diff --git a/package.json b/package.json index 752e8e4..9a3f376 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "babel-eslint": "^7.1.1", + "clone": "^2.1.0", "cross-spawn": "^5.0.0", "eslint": "^3.0.1", "eslint-config-standard": "^6.0.0-beta.0", @@ -24,6 +25,8 @@ "eslint-plugin-react": "^6.0.0", "eslint-plugin-standard": "^2.0.0", "mkdirp": "^0.5.0", + "mock-require": "^2.0.0", + "require-uncached": "^1.0.3", "run-parallel-limit": "^1.0.1", "standard": "*", "standard-packages": "^3.0.14", diff --git a/test/api.js b/test/api.js index 3c2344c..4e31e06 100644 --- a/test/api.js +++ b/test/api.js @@ -1,12 +1,19 @@ var eslint = require('eslint') -var Linter = require('../').linter var test = require('tape') +const mock = require('mock-require') +const requireUncached = require('require-uncached') +const clone = require('clone') + +const defaultOpts = { + eslint, + eslintConfig: require('../tmp/standard/options').eslintConfig +} + +const getLinter = () => requireUncached('../').linter function getStandard () { - return new Linter({ - eslint: eslint, - eslintConfig: require('../tmp/standard/options').eslintConfig - }) + const Linter = getLinter() + return new Linter(defaultOpts) } test('api: lintFiles', function (t) { @@ -44,3 +51,63 @@ test('api: parseOpts -- avoid self.eslintConfig global mutation', function (t) { t.deepEqual(opts.globals, ['what']) t.deepEqual(standard.eslintConfig.globals, []) }) + +const defaultWhitelist = ['foo', 'bar'] +const defaultWhitelistPkgs = ['eslint-config-foo', 'eslint-config-bar'] + +test('api: whitelist -- provided whitelist, no existing extends', function (t) { + t.plan(1) + const opts = clone(defaultOpts) + opts.whitelist = clone(defaultWhitelist) + defaultWhitelistPkgs.forEach(name => mock(name, {})) + const Linter = getLinter() + let linter = new Linter(opts) + t.deepEqual(linter.eslintConfig.extends, defaultWhitelist) + defaultWhitelistPkgs.forEach(name => mock.stop(name)) +}) + +test('api: whitelist -- provided whitelist, string existing extends', function (t) { + t.plan(1) + const opts = clone(defaultOpts) + opts.whitelist = clone(defaultWhitelist) + opts.eslintConfig.extends = 'baz' + defaultWhitelistPkgs.forEach(name => mock(name, {})) + const Linter = getLinter() + let linter = new Linter(opts) + const expected = ['baz'].concat(defaultWhitelist) + t.deepEqual(linter.eslintConfig.extends, expected) + defaultWhitelistPkgs.forEach(name => mock.stop(name)) +}) + +test('api: whitelist -- provided whitelist, multiple existing extends', function (t) { + t.plan(1) + const opts = clone(defaultOpts) + opts.whitelist = clone(defaultWhitelist) + opts.eslintConfig.extends = ['baz', 'yad'] + defaultWhitelistPkgs.forEach(name => mock(name, {})) + const Linter = getLinter() + let linter = new Linter(opts) + const expected = ['baz', 'yad'].concat(defaultWhitelist) + t.deepEqual(linter.eslintConfig.extends, expected) + defaultWhitelistPkgs.forEach(name => mock.stop(name)) +}) + +test('api: whitelist -- packages not installed', function (t) { + t.plan(1) + const opts = clone(defaultOpts) + opts.whitelist = clone(defaultWhitelist) + const Linter = getLinter() + let linter = new Linter(opts) + t.equals(linter.eslintConfig.extends, undefined) +}) + +test('api: whitelist -- some packages installed', function (t) { + t.plan(1) + const opts = clone(defaultOpts) + opts.whitelist = clone(defaultWhitelist) + const Linter = getLinter() + mock(defaultWhitelistPkgs[0], {}) + let linter = new Linter(opts) + t.deepEqual(linter.eslintConfig.extends, defaultWhitelist.slice(0, 1)) + mock.stop(defaultWhitelistPkgs[0]) +})