From f656ee3cbfa07c2cbb5d306631f85aba72ad1ef7 Mon Sep 17 00:00:00 2001 From: Daniel Lupu Date: Wed, 11 Mar 2020 12:44:48 +0200 Subject: [PATCH] Add `webpack` option (#375) Co-authored-by: Sindre Sorhus --- lib/options-manager.js | 45 ++++++++++++++-- package.json | 5 +- readme.md | 15 +++++- test/fixtures/webpack/no-config/file1.js | 2 + test/fixtures/webpack/no-config/file2.js | 2 + test/fixtures/webpack/no-config/file3.js | 1 + test/fixtures/webpack/with-config/file1.js | 2 + test/fixtures/webpack/with-config/file2.js | 2 + .../webpack/with-config/webpack.config.js | 9 ++++ test/lint-files.js | 52 +++++++++++++++++++ test/options-manager.js | 31 ++++++++++- 11 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/webpack/no-config/file1.js create mode 100644 test/fixtures/webpack/no-config/file2.js create mode 100644 test/fixtures/webpack/no-config/file3.js create mode 100644 test/fixtures/webpack/with-config/file1.js create mode 100644 test/fixtures/webpack/with-config/file2.js create mode 100644 test/fixtures/webpack/with-config/webpack.config.js diff --git a/lib/options-manager.js b/lib/options-manager.js index 686b052f..c765cb54 100644 --- a/lib/options-manager.js +++ b/lib/options-manager.js @@ -8,6 +8,7 @@ const mergeWith = require('lodash/mergeWith'); const groupBy = require('lodash/groupBy'); const flow = require('lodash/flow'); const pathExists = require('path-exists'); +const findUp = require('find-up'); const findCacheDir = require('find-cache-dir'); const resolveFrom = require('resolve-from'); const prettier = require('prettier'); @@ -313,14 +314,13 @@ const buildXOConfig = options => config => { Object.assign(config.rules, options.rules); } - if (options.settings) { - config.baseConfig.settings = options.settings; - } - if (options.parser) { config.baseConfig.parser = options.parser; } + config.baseConfig.settings = options.settings || {}; + config.baseConfig.settings['import/resolver'] = gatherImportResolvers(options); + return config; }; @@ -476,6 +476,43 @@ const findApplicableOverrides = (path, overrides) => { const getIgnores = ({ignores}) => DEFAULT_IGNORES.concat(ignores || []); +const gatherImportResolvers = options => { + let resolvers = {}; + + const resolverSettings = options.settings && options.settings['import/resolver']; + if (resolverSettings) { + if (typeof resolverSettings === 'string') { + resolvers[resolverSettings] = {}; + } else { + resolvers = {...resolverSettings}; + } + } + + let webpackResolverSettings; + + if (options.webpack) { + webpackResolverSettings = options.webpack === true ? {} : options.webpack; + } else if (!(options.webpack === false || resolvers.webpack)) { + // If a webpack config file exists, add the import resolver automatically + const webpackConfigPath = findUp.sync('webpack.config.js', {cwd: options.cwd}); + if (webpackConfigPath) { + webpackResolverSettings = {config: webpackConfigPath}; + } + } + + if (webpackResolverSettings) { + resolvers = { + ...resolvers, + webpack: { + ...resolvers.webpack, + ...webpackResolverSettings + } + }; + } + + return resolvers; +}; + module.exports = { findApplicableOverrides, mergeWithPrettierConfig, diff --git a/package.json b/package.json index 901c1533..6be08339 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "eslint-config-xo": "^0.29.0", "eslint-config-xo-typescript": "^0.26.0", "eslint-formatter-pretty": "^3.0.1", + "eslint-import-resolver-webpack": "^0.12.1", "eslint-plugin-ava": "^10.0.1", "eslint-plugin-eslint-comments": "^3.1.2", "eslint-plugin-import": "^2.20.1", @@ -70,6 +71,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-unicorn": "^17.2.0", "find-cache-dir": "^3.0.0", + "find-up": "^4.1.0", "fs-extra": "^8.1.0", "get-stdin": "^7.0.0", "globby": "^9.0.0", @@ -103,7 +105,8 @@ "nyc": "^15.0.0", "pify": "^4.0.0", "proxyquire": "^2.1.3", - "temp-write": "^4.0.0" + "temp-write": "^4.0.0", + "webpack": "^4.42.0" }, "eslintConfig": { "extends": "eslint-config-xo" diff --git a/readme.md b/readme.md index ba2da73f..6f5f2218 100644 --- a/readme.md +++ b/readme.md @@ -252,7 +252,7 @@ Allow more extensions to be linted besides `.js` and `.jsx`. Make sure they're s Type: `object` -[Shared ESLint settings](https://eslint.org/docs/user-guide/configuring#adding-shared-settings) exposed to rules. For example, to configure the [`import`](https://github.com/benmosher/eslint-plugin-import#settings) plugin to use your webpack configuration for determining search paths, you can put `{"import/resolver": "webpack"}` here. +[Shared ESLint settings](https://eslint.org/docs/user-guide/configuring#adding-shared-settings) exposed to rules. ### parser @@ -269,6 +269,17 @@ Enforce ES2015+ rules. Disabling this will make it not *enforce* ES2015+ syntax *ES2015+ is parsed even without this option. You can already use ES2017 features like [`async`/`await`](https://github.com/lukehoban/ecmascript-asyncawait). +### webpack + +Type: `boolean | object` +Default: `false` + +Use [eslint-import-resolver-webpack](https://github.com/benmosher/eslint-plugin-import/tree/master/resolvers/webpack) to resolve import search paths. This is enabled automatically if a `webpack.config.js` file is found. + +Set this to a boolean to explicitly enable or disable the resolver. + +Setting this to an object enables the resolver and passes the object as configuration. See the [resolver readme](https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/webpack/README.md) along with the [webpack documentation](https://webpack.js.org/configuration/resolve/) for more information. + ## TypeScript and Flow ### TypeScript @@ -368,7 +379,7 @@ For example, if your project targets Node.js 8 but you want to use the latest Ja } ``` -This way your `package.json` will contain the actual minimum Node.js version supported by your published code, but XO will lint your source code as if it targets Node.js 12. +This way your `package.json` will contain the actual minimum Node.js version supported by your published code, but XO will lint your source code as if it targets Node.js 12. ### Including files ignored by default diff --git a/test/fixtures/webpack/no-config/file1.js b/test/fixtures/webpack/no-config/file1.js new file mode 100644 index 00000000..08dcc995 --- /dev/null +++ b/test/fixtures/webpack/no-config/file1.js @@ -0,0 +1,2 @@ +import _ from 'file2alias'; // eslint-disable-line no-unused-vars +import __ from 'inexistent'; // eslint-disable-line no-unused-vars diff --git a/test/fixtures/webpack/no-config/file2.js b/test/fixtures/webpack/no-config/file2.js new file mode 100644 index 00000000..71b67088 --- /dev/null +++ b/test/fixtures/webpack/no-config/file2.js @@ -0,0 +1,2 @@ +const foo = 1; +export default foo; diff --git a/test/fixtures/webpack/no-config/file3.js b/test/fixtures/webpack/no-config/file3.js new file mode 100644 index 00000000..49b9264f --- /dev/null +++ b/test/fixtures/webpack/no-config/file3.js @@ -0,0 +1 @@ +import _ from '!./file2'; // eslint-disable-line no-unused-vars diff --git a/test/fixtures/webpack/with-config/file1.js b/test/fixtures/webpack/with-config/file1.js new file mode 100644 index 00000000..08dcc995 --- /dev/null +++ b/test/fixtures/webpack/with-config/file1.js @@ -0,0 +1,2 @@ +import _ from 'file2alias'; // eslint-disable-line no-unused-vars +import __ from 'inexistent'; // eslint-disable-line no-unused-vars diff --git a/test/fixtures/webpack/with-config/file2.js b/test/fixtures/webpack/with-config/file2.js new file mode 100644 index 00000000..71b67088 --- /dev/null +++ b/test/fixtures/webpack/with-config/file2.js @@ -0,0 +1,2 @@ +const foo = 1; +export default foo; diff --git a/test/fixtures/webpack/with-config/webpack.config.js b/test/fixtures/webpack/with-config/webpack.config.js new file mode 100644 index 00000000..12e90935 --- /dev/null +++ b/test/fixtures/webpack/with-config/webpack.config.js @@ -0,0 +1,9 @@ +const path = require('path'); + +module.exports = { + resolve: { + alias: { + file2alias: path.resolve(__dirname, 'file2.js') + } + } +}; diff --git a/test/lint-files.js b/test/lint-files.js index e7cdb10b..593ab917 100644 --- a/test/lint-files.js +++ b/test/lint-files.js @@ -196,6 +196,58 @@ test('typescript files', async t => { ); }); +test('webpack import resolver is used if webpack.config.js is found', async t => { + const cwd = 'fixtures/webpack/with-config/'; + const {results} = await fn.lintFiles(path.resolve(cwd, 'file1.js'), { + cwd, + rules: { + 'import/no-unresolved': 2 + } + }); + + t.is(results[0].errorCount, 1); + + const errorMessage = results[0].messages[0].message; + t.truthy(/Unable to resolve path to module 'inexistent'/.exec(errorMessage)); +}); + +test('webpack import resolver config can be passed through webpack option', async t => { + const cwd = 'fixtures/webpack/no-config/'; + + const {results} = await fn.lintFiles(path.resolve(cwd, 'file1.js'), { + cwd, + webpack: { + config: { + resolve: { + alias: { + file2alias: path.resolve(__dirname, cwd, './file2.js') + } + } + } + }, + rules: { + 'import/no-unresolved': 2 + } + }); + + t.is(results[0].errorCount, 1); +}); + +test('webpack import resolver is used if {webpack: true}', async t => { + const cwd = 'fixtures/webpack/no-config/'; + + const {results} = await fn.lintFiles(path.resolve(cwd, 'file3.js'), { + cwd, + webpack: true, + rules: { + 'import/no-unresolved': 2, + 'import/no-webpack-loader-syntax': 0 + } + }); + + t.is(results[0].errorCount, 0); +}); + async function configType(t, {dir}) { const {results} = await fn.lintFiles('**/*', {cwd: path.resolve('fixtures', 'config-files', dir)}); diff --git a/test/options-manager.js b/test/options-manager.js index e357587a..e277b58a 100644 --- a/test/options-manager.js +++ b/test/options-manager.js @@ -384,11 +384,40 @@ test('buildConfig: parser', t => { }); test('buildConfig: settings', t => { - const settings = {'import/resolver': 'webpack'}; + const settings = {'import/resolver': {webpack: {}}}; const config = manager.buildConfig({settings}); t.deepEqual(config.baseConfig.settings, settings); }); +test('buildConfig: finds webpack config file', t => { + const cwd = path.resolve('fixtures', 'webpack', 'with-config'); + const config = manager.buildConfig({cwd}); + const expected = {webpack: {config: path.resolve(cwd, 'webpack.config.js')}}; + t.deepEqual(config.baseConfig.settings['import/resolver'], expected); +}); + +test('buildConfig: webpack option sets resolver', t => { + const config = manager.buildConfig({webpack: true, settings: {'import/resolver': 'node'}}); + t.deepEqual(config.baseConfig.settings['import/resolver'], {webpack: {}, node: {}}); +}); + +test('buildConfig: webpack option handles object values', t => { + const config = manager.buildConfig({webpack: {foo: 1}, settings: {'import/resolver': 'node'}}); + t.deepEqual(config.baseConfig.settings['import/resolver'], {webpack: {foo: 1}, node: {}}); +}); + +test('buildConfig: webpack resolver is not added automatically if webpack option is set to false', t => { + const cwd = path.resolve('fixtures', 'webpack', 'with-config'); + const config = manager.buildConfig({cwd, webpack: false, settings: {}}); + t.deepEqual(config.baseConfig.settings['import/resolver'], {}); +}); + +test('buildConfig: webpack option is merged with import/resolver', t => { + const settings = {'import/resolver': {webpack: {bar: 1}}}; + const config = manager.buildConfig({settings, webpack: {foo: 1}}); + t.deepEqual(config.baseConfig.settings['import/resolver'], {webpack: {foo: 1, bar: 1}}); +}); + test('buildConfig: extends', t => { const config = manager.buildConfig({extends: [ 'plugin:foo/bar',