diff --git a/README.md b/README.md index 41dc1ec..a2e15d6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The intended usage is as an npm script: { ... "scripts": { - "find-new-rules": "eslint-find-new-rules eslint-config-yourConfigName" + "find-new-rules": "eslint-find-new-rules path/to/eslint-config" } ... } @@ -65,6 +65,27 @@ It will also default to the `main` in your `package.json`, so you can omit the a eslint-find-new-rules ``` +### As a `require`d module + +``` +var getRuleFinder = require('./eslint-find-new-rules') +var ruleFinder = getRuleFinder('path/to/eslint-config') + +// default to the `main` in your `package.json` +// var ruleFinder = getRuleFinder() + +// get all the current, plugin, available and unused rules +// without referring the extended files or documentation + +ruleFinder.getCurrentRules() + +ruleFinder.getPluginRules() + +ruleFinder.getAllAvailableRules() + +ruleFinder.getUnusedRules() +``` + ## Contributors Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): diff --git a/bin-utils.js b/bin-utils.js deleted file mode 100644 index 234525c..0000000 --- a/bin-utils.js +++ /dev/null @@ -1,68 +0,0 @@ -var path = require('path') -var isAbsolute = require('path-is-absolute') -var eslint = require('eslint') - -function resolvePackagesMain(cwd, packageFile) { - var packageFilePath = path.join(cwd, packageFile) - var packageJson = require(packageFilePath) - return packageJson.main -} - -function getConfigFile(specifiedFile) { - //var specifiedFile = process.argv[2] - if (specifiedFile) { - // this is being called like: eslint-find-new-rules eslint-config-mgol - if (isAbsolute(specifiedFile)) { - return specifiedFile - } else { - return path.join(process.cwd(), specifiedFile) - } - } else { - // this is not being called with an arg. Use the package.json `main` - return resolvePackagesMain(process.cwd(), 'package.json') - } -} - -function getConfig(specifiedFile) { - var configFile = getConfigFile(specifiedFile) - var cliEngine = new eslint.CLIEngine({ - // ignore any config applicable depending on the location on the filesystem - useEslintrc: false, - // point to the particular config - configFile: configFile, // eslint-disable-line object-shorthand - }) - return cliEngine.getConfigForFile() -} - -function mapPluginRuleNames(plugin) { - return function mapPluginNames(rule) { - return plugin + '/' + rule - } -} - -function getCurrentRules(conf) { - var rules = Object.keys(conf.rules) - return rules -} - -function getPluginRules(conf) { - var rules = [] - var plugins = conf.plugins - if (plugins) { - plugins.forEach(function normalizePluginRule(plugin) { - var thisPluginsConfig = require('eslint-plugin-' + plugin) - var thisPluginsRules = thisPluginsConfig.rules - /* istanbul ignore next */ - if (typeof thisPluginsRules === 'object') { - rules = rules.concat(Object.keys(thisPluginsRules).map(mapPluginRuleNames(plugin))) - } - }) - } - return rules -} - -module.exports = { - getConfig: getConfig, // eslint-disable-line object-shorthand - getCurrentRules: getCurrentRules, // eslint-disable-line object-shorthand - getPluginRules: getPluginRules, // eslint-disable-line object-shorthand -} diff --git a/index.js b/index.js deleted file mode 100644 index 51bb40e..0000000 --- a/index.js +++ /dev/null @@ -1,19 +0,0 @@ -var fs = require('fs') -var difference = require('lodash.difference') - -module.exports = findNewRules - -function findNewRules(currentRules, pluginRules) { - var allRules = fs - .readdirSync('./node_modules/eslint/lib/rules') - .map(function removeJsFromFilename(filename) { - return filename.replace(/\.js$/, '') - }) - - if (pluginRules) { - allRules = allRules.concat(pluginRules) - } - - return difference(allRules, currentRules) -} - diff --git a/package.json b/package.json index cafa294..8b50f00 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "eslint-find-new-rules", "version": "0.0.0-semantically-released", "description": "Find built-in ESLint rules you don't have in your custom config.", - "main": "index.js", + "main": "src/rule-finder.js", "scripts": { "cover": "nyc --reporter=lcov --reporter=text ava", "test": "ava", @@ -16,7 +16,7 @@ "report-coverage": "cat ./coverage/lcov.info | node_modules/.bin/codecov" }, "bin": { - "eslint-find-new-rules": "bin.js" + "eslint-find-new-rules": "src/bin.js" }, "keywords": [], "author": "Michał Gołębiowski ", @@ -78,4 +78,4 @@ "url": "https://github.com/kentcdodds/eslint-find-new-rules/issues" }, "homepage": "https://github.com/kentcdodds/eslint-find-new-rules#readme" -} +} \ No newline at end of file diff --git a/bin.js b/src/bin.js similarity index 54% rename from bin.js rename to src/bin.js index d369117..80508ad 100755 --- a/bin.js +++ b/src/bin.js @@ -4,14 +4,11 @@ // Prints rules recognized by ESLint that don't appear in the given config // preset. It helps with upgrading the preset when new ESLint gets released. -var binUtils = require('./bin-utils') -var findNewRules = require('./index') +var getRuleFinder = require('./rule-finder') +var specifiedFile = process.argv[2] +var ruleFinder = getRuleFinder(specifiedFile) -var config = binUtils.getConfig(process.argv[2]) -var currentRules = binUtils.getCurrentRules(config) -var pluginRules = binUtils.getPluginRules(config) - -var newRules = findNewRules(currentRules, pluginRules) +var newRules = ruleFinder.getUnusedRules() if (newRules.length) { console.log('New rules to add to the config: ' + newRules.join(', ') + '.') // eslint-disable-line no-console diff --git a/src/rule-finder.js b/src/rule-finder.js new file mode 100644 index 0000000..275d33a --- /dev/null +++ b/src/rule-finder.js @@ -0,0 +1,95 @@ +var path = require('path') +var fs = require('fs') + +var eslint = require('eslint') +var isAbsolute = require('path-is-absolute') +var difference = require('lodash.difference') + +function _getConfigFile(specifiedFile) { + if (specifiedFile) { + if (isAbsolute(specifiedFile)) { + return specifiedFile + } else { + return path.join(process.cwd(), specifiedFile) + } + } else { + // this is not being called with an arg. Use the package.json `main` + return require(path.join(process.cwd(), 'package.json')).main + } +} + +function _getConfig(configFile) { + var cliEngine = new eslint.CLIEngine({ + // ignore any config applicable depending on the location on the filesystem + useEslintrc: false, + // point to the particular config + configFile: configFile, // eslint-disable-line object-shorthand + }) + return cliEngine.getConfigForFile() +} + +function _getCurrentRules(config) { + return Object.keys(config.rules) +} + +function _getPluginRules(config) { + var pluginRules = [] + var plugins = config.plugins + if (plugins) { + plugins.forEach(function getPluginRule(plugin) { + var pluginConfig = require('eslint-plugin-' + plugin) + var rules = pluginConfig.rules + pluginRules = pluginRules.concat( + Object.keys(rules).map(function normalizePluginRule(rule) { + return plugin + '/' + rule + }) + ) + }) + } + return pluginRules +} + +function _getAllAvailableRules(pluginRules) { + var allRules = fs + .readdirSync('./node_modules/eslint/lib/rules') + .map(function removeJsFromFilename(filename) { + return filename.replace(/\.js$/, '') + }) + + allRules = allRules.concat(pluginRules) + + return allRules +} + +function RuleFinder(specifiedFile) { + var configFile = _getConfigFile(specifiedFile) + var config = _getConfig(configFile) + var currentRules = _getCurrentRules(config) + var pluginRules = _getPluginRules(config) + var allRules = _getAllAvailableRules(pluginRules) + var unusedRules = difference(allRules, currentRules) + + // get all the current rules instead of referring the extended files or documentation + this.getCurrentRules = function getCurrentRules() { + return currentRules + } + + // get all the plugin rules instead of referring the extended files or documentation + this.getPluginRules = function getPluginRules() { + return pluginRules + } + + // get all the availale rules instead of referring eslint and pluging packages or documentation + this.getAllAvailableRules = function getAllAvailableRules() { + return allRules + } + + this.getUnusedRules = function getUnusedRules() { + return unusedRules + } + +} + +module.exports = function getRuleFinder(specifiedFile) { + return new RuleFinder(specifiedFile) +} diff --git a/test/bin-utils.js b/test/bin-utils.js deleted file mode 100644 index b4080b8..0000000 --- a/test/bin-utils.js +++ /dev/null @@ -1,84 +0,0 @@ -import test from 'ava' -import path from 'path' -import proxyquire from 'proxyquire' - -const {getConfig, getCurrentRules, getPluginRules} = proxyquire('../bin-utils', { - 'eslint-plugin-react': { - rules: { - 'foo-rule': true, - 'bar-rule': true, - 'baz-rule': true, - }, - '@noCallThru': true, - '@global': true, - }, -}) - -const processCwd = process.cwd - -const noSpecifiedFile = path.resolve(process.cwd(), './fixtures/no-path') -const specifiedFileRelative = './fixtures/.eslintrc.js' -const specifiedFileAbsolute = path.join(process.cwd(), specifiedFileRelative) - - -test.beforeEach(() => { - process.argv.splice(2, process.argv.length) -}) - -test.afterEach(() => { - process.cwd = processCwd -}) - - -test('determine config, rules and plugins, no path', t => { - process.cwd = () => noSpecifiedFile - - const expectedRuleSet = ['no-alert'] - const expectedPluginRuleSet = [] - - const retrievedConfig = getConfig() - const retrievedRules = getCurrentRules(retrievedConfig) - const possiblePluginRules = getPluginRules(retrievedConfig) - - t.true(typeof retrievedConfig === 'object', 'config object should be retrieved') - - t.true(typeof retrievedConfig.rules === 'object', 'config object should contain rules') - t.same(retrievedRules, expectedRuleSet, 'retrieved rules should equal expected rules') - - t.true(typeof retrievedConfig.plugins === 'undefined', 'config object should not contain plugins') - t.same(possiblePluginRules, expectedPluginRuleSet, 'retrieved plugin rules should equal expected plugin rules') -}) - -test('determine config, rules and plugins, relative path', t => { - const expectedRuleSet = ['no-console'] - const expectedPluginRuleSet = ['react/foo-rule', 'react/bar-rule', 'react/baz-rule'] - - const retrievedConfig = getConfig(specifiedFileRelative) - const retrievedRules = getCurrentRules(retrievedConfig) - const possiblePluginRules = getPluginRules(retrievedConfig) - - t.true(typeof retrievedConfig === 'object', 'config object should be retrieved') - - t.true(typeof retrievedConfig.rules === 'object', 'config object should contain rules') - t.same(retrievedRules, expectedRuleSet, 'retrieved rules should equal expected rules') - - t.same(retrievedConfig.plugins, ['react'], 'retrieved plugins should contain only "react"') - t.same(possiblePluginRules, expectedPluginRuleSet, 'retrieved plugin rules should equal expected plugin rules') -}) - -test('determine config, rules and plugins, absolute path', t => { - const expectedRuleSet = ['no-console'] - const expectedPluginRuleSet = ['react/foo-rule', 'react/bar-rule', 'react/baz-rule'] - - const retrievedConfig = getConfig(specifiedFileAbsolute) - const retrievedRules = getCurrentRules(retrievedConfig) - const possiblePluginRules = getPluginRules(retrievedConfig) - - t.true(typeof retrievedConfig === 'object', 'config object should be retrieved') - - t.true(typeof retrievedConfig.rules === 'object', 'config object should contain rules') - t.same(retrievedRules, expectedRuleSet, 'retrieved rules should equal expected rules') - - t.same(retrievedConfig.plugins, ['react'], 'retrieved plugins should contain only "react"') - t.same(possiblePluginRules, expectedPluginRuleSet, 'retrieved plugin rules should equal expected plugin rules') -}) diff --git a/test/fixtures/.eslintrc.js b/test/fixtures/.eslintrc.js deleted file mode 100644 index 3e060c9..0000000 --- a/test/fixtures/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - rules: { - "no-console": [2], - }, - "plugins": [ - "react" - ] -} diff --git a/test/fixtures/base-config.yml b/test/fixtures/base-config.yml deleted file mode 100644 index 0509a62..0000000 --- a/test/fixtures/base-config.yml +++ /dev/null @@ -1,5 +0,0 @@ -rules: - no-console: - - 2 - no-alert: - - 2 diff --git a/test/fixtures/eslint.json b/test/fixtures/eslint.json new file mode 100644 index 0000000..2b4ac5a --- /dev/null +++ b/test/fixtures/eslint.json @@ -0,0 +1,6 @@ +{ + "extends": "./eslint.yml", + "plugins": [ + "react" + ] +} diff --git a/test/fixtures/eslint.yml b/test/fixtures/eslint.yml new file mode 100644 index 0000000..c39e9d5 --- /dev/null +++ b/test/fixtures/eslint.yml @@ -0,0 +1,5 @@ +extends: + ./eslintrc +rules: + bar-rule: + - 2 diff --git a/test/fixtures/eslintrc b/test/fixtures/eslintrc new file mode 100644 index 0000000..157df27 --- /dev/null +++ b/test/fixtures/eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "foo-rule": [2] + } +} diff --git a/test/fixtures/extending-config.json b/test/fixtures/extending-config.json deleted file mode 100644 index 70e88bb..0000000 --- a/test/fixtures/extending-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./base-config.yml", - "rules": { - "comma-dangle": [2, "never"] - } -} diff --git a/test/fixtures/no-path/index.js b/test/fixtures/no-path/index.js index e4b82ae..e853bb4 100644 --- a/test/fixtures/no-path/index.js +++ b/test/fixtures/no-path/index.js @@ -1,5 +1,5 @@ module.exports = { rules: { - 'no-alert': [2], - }, + 'foo-rule': [2] + } } diff --git a/test/fixtures/no-path/package.json b/test/fixtures/no-path/package.json index 3c2fce3..05749d7 100644 --- a/test/fixtures/no-path/package.json +++ b/test/fixtures/no-path/package.json @@ -3,10 +3,5 @@ "version": "0.0.0", "description": "Simulating a project's package.json.", "private": true, - "main": "./index.js", - "eslintConfig": { - "rules": { - "no-console": [2] - } - } + "main": "./index.js" } diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 510ea2a..0000000 --- a/test/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import proxyquire from 'proxyquire' -import test from 'ava' - -const findNewRules = proxyquire('../index', { - fs: { - readdirSync: () => ['foo-rule.js', 'bar-rule.js', 'baz-thing.js'], - }, -}) - -test('returns the difference between what it finds in eslint/lib/rules and the rules array it is passed', t => { - const missingRules = findNewRules(['baz-thing', 'foo-rule'], ['react-foo']) - t.same(missingRules, ['bar-rule', 'react-foo']) -}) - -test('returns an empty array if there is no difference', t => { - const missingRules = findNewRules(['baz-thing', 'foo-rule', 'bar-rule']) - t.true(Array.isArray(missingRules)) - t.same(missingRules.length, 0) -}) diff --git a/test/rule-finder.js b/test/rule-finder.js new file mode 100644 index 0000000..b3a24f0 --- /dev/null +++ b/test/rule-finder.js @@ -0,0 +1,99 @@ +import path from 'path' + +import test from 'ava' +import proxyquire from 'proxyquire' + +const processCwd = process.cwd + +const getRuleFinder = proxyquire('../src/rule-finder', { + fs: { + readdirSync: () => ['foo-rule.js', 'bar-rule.js', 'baz-rule.js'], + }, + 'eslint-plugin-react': { + rules: { + 'foo-rule': true, + 'bar-rule': true, + 'baz-rule': true, + }, + '@noCallThru': true, + '@global': true, + }, +}) + +const noSpecifiedFile = path.resolve(process.cwd(), './fixtures/no-path') +const specifiedFileRelative = './fixtures/eslint.json' +const specifiedFileAbsolute = path.join(process.cwd(), specifiedFileRelative) + +test.afterEach(() => { + process.cwd = processCwd +}) + +test('no specifiedFile is passed to the constructor', (t) => { + process.cwd = () => noSpecifiedFile + const ruleFinder = getRuleFinder() + t.same(ruleFinder.getUnusedRules(), ['bar-rule', 'baz-rule']) +}) + +test('no specifiedFile - curent rules', (t) => { + process.cwd = () => noSpecifiedFile + const ruleFinder = getRuleFinder() + t.same(ruleFinder.getCurrentRules(), ['foo-rule']) +}) + +test('no specifiedFile - plugin rules', (t) => { + process.cwd = () => noSpecifiedFile + const ruleFinder = getRuleFinder() + t.same(ruleFinder.getPluginRules(), []) +}) + +test('no specifiedFile - all available rules', (t) => { + process.cwd = () => noSpecifiedFile + const ruleFinder = getRuleFinder() + t.same(ruleFinder.getAllAvailableRules(), ['foo-rule', 'bar-rule', 'baz-rule']) +}) + +test('specifiedFile (relative path) is passed to the constructor', (t) => { + const ruleFinder = getRuleFinder(specifiedFileRelative) + t.same(ruleFinder.getUnusedRules(), ['baz-rule', 'react/foo-rule', 'react/bar-rule', 'react/baz-rule']) +}) + +test('specifiedFile (relative path) - curent rules', (t) => { + const ruleFinder = getRuleFinder(specifiedFileRelative) + t.same(ruleFinder.getCurrentRules(), ['foo-rule', 'bar-rule']) +}) + +test('specifiedFile (relative path) - plugin rules', (t) => { + const ruleFinder = getRuleFinder(specifiedFileRelative) + t.same(ruleFinder.getPluginRules(), ['react/foo-rule', 'react/bar-rule', 'react/baz-rule']) +}) + +test('specifiedFile (relative path) - all available rules', (t) => { + const ruleFinder = getRuleFinder(specifiedFileRelative) + t.same( + ruleFinder.getAllAvailableRules(), + ['foo-rule', 'bar-rule', 'baz-rule', 'react/foo-rule', 'react/bar-rule', 'react/baz-rule'] + ) +}) + +test('specifiedFile (absolut path) is passed to the constructor', (t) => { + const ruleFinder = getRuleFinder(specifiedFileAbsolute) + t.same(ruleFinder.getUnusedRules(), ['baz-rule', 'react/foo-rule', 'react/bar-rule', 'react/baz-rule']) +}) + +test('specifiedFile (absolut path) - curent rules', (t) => { + const ruleFinder = getRuleFinder(specifiedFileAbsolute) + t.same(ruleFinder.getCurrentRules(), ['foo-rule', 'bar-rule']) +}) + +test('specifiedFile (absolut path) - plugin rules', (t) => { + const ruleFinder = getRuleFinder(specifiedFileAbsolute) + t.same(ruleFinder.getPluginRules(), ['react/foo-rule', 'react/bar-rule', 'react/baz-rule']) +}) + +test('specifiedFile (absolut path) - all available rules', (t) => { + const ruleFinder = getRuleFinder(specifiedFileAbsolute) + t.same( + ruleFinder.getAllAvailableRules(), + ['foo-rule', 'bar-rule', 'baz-rule', 'react/foo-rule', 'react/bar-rule', 'react/baz-rule'] + ) +})