Skip to content

Commit ba177af

Browse files
committed
feature #985 Move from eslint-loader to eslint-webpack-plugin, close #847 (Kocal, weaverryan)
This PR was squashed before being merged into the main branch. Discussion ---------- Move from eslint-loader to eslint-webpack-plugin, close #847 Hi 👋 This PR is a proposal for #847 which add the eslint-webpack-plugin, since the eslint-loader is deprecated. There are a lot of changes: - ~**BC** method `.enableEslintLoader()` has been removed, in favor of `.enableEslintPlugin()`. It accepts an object or a function.~ `.enableEslintLoader()` has not been removed, so it's not a BC anymore - **BC** ESLint <7 support has been dropped, since the plugin requires ESLint >= 7. - **BC, I guess** No more Encore-specific options, the only option `lintVue` was too specific and not futur proof (if you want to lint GraphQL, JSON or whatever you want, with ESLint, see last § of #574 (comment)). Extensions can easily be configured through `Encore.enableEslintPlugin(options => { options.extensions.push('vue'); })` - `eslint.CLIEngine` is not used anymore to detect if an ESLint configuration exists, because this class is deprecated since ESLint 7. ~However, some tests are failing but I don't know why:~ ``` [ { moduleIdentifier: '/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js??ruleSet[1].rules[0].use[0]!/tmp/err9jr/js/eslint.js', moduleName: './js/eslint.js', message: 'Module build failed (from ../../home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js):\n' + "Error: Cannot find module '`@babel`/plugin-syntax-dynamic-import'\n" + 'Require stack:\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/index.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/index.js\n' + '- /home/hugo/workspace-os/webpack-encore/lib/config/parse-runtime.js\n' + '- /home/hugo/workspace-os/webpack-encore/test/helpers/setup.js\n' + '- /home/hugo/workspace-os/webpack-encore/test/bin/encore.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/esm-utils.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/mocha.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/cli/one-and-dones.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/cli/options.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/bin/mocha\n' + ' at Function.Module._resolveFilename (internal/modules/cjs/loader.js:831:15)\n' + ' at resolve (internal/modules/cjs/helpers.js:80:19)\n' + ' at resolveStandardizedName (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js:96:7)\n' + ' at resolvePlugin (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js:40:10)\n' + ' at loadPlugin (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js:48:20)\n' + ' at loadPlugin.next (<anonymous>)\n' + ' at createDescriptor (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:179:16)\n' + ' at createDescriptor.next (<anonymous>)\n' + ' at step (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:261:32)\n' + ' at evaluateAsync (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:291:5)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:44:11\n' + ' at Array.forEach (<anonymous>)\n' + ' at Function.async (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:43:15)\n' + ' at Function.all (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:216:13)\n' + ' at Generator.next (<anonymous>)\n' + ' at createDescriptors (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:134:38)\n' + ' at createDescriptors.next (<anonymous>)\n' + ' at createPluginDescriptors (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:130:17)\n' + ' at createPluginDescriptors.next (<anonymous>)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:86:32\n' + ' at Generator.next (<anonymous>)\n' + ' at Function.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/gensync-utils/async.js:16:3)\n' + ' at Generator.next (<anonymous>)\n' + ' at step (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:269:25)\n' + ' at evaluateAsync (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:291:5)\n' + ' at Function.errback (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:113:7)\n' + ' at errback (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/gensync-utils/async.js:60:18)\n' + ' at async (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:188:31)\n' + ' at onFirstPause (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:216:13)\n' + ' at Generator.next (<anonymous>)\n' + ' at cachedFunction (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/caching.js:58:46)\n' + ' at cachedFunction.next (<anonymous>)\n' + ' at mergeChainOpts (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-chain.js:405:34)\n' + ' at mergeChainOpts.next (<anonymous>)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-chain.js:364:14\n' + ' at Generator.next (<anonymous>)\n' + ' at buildRootChain (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-chain.js:57:36)\n' + ' at buildRootChain.next (<anonymous>)\n' + ' at loadPrivatePartialConfig (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/partial.js:85:62)\n' + ' at loadPrivatePartialConfig.next (<anonymous>)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/partial.js:131:25\n' + ' at Generator.next (<anonymous>)\n' + ' at step (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:269:25)\n' + ' at evaluateAsync (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:291:5)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:93:9\n' + ' at new Promise (<anonymous>)\n' + ' at async (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:92:14)\n' + ' at Object.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:155:26)\n' + ' at Generator.next (<anonymous>)\n' + ' at asyncGeneratorStep (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:3:103)\n' + ' at _next (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:5:194)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:5:364\n' + ' at new Promise (<anonymous>)\n' + ' at Object.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:5:97)\n' + ' at Object.loader (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:64:18)\n' + ' at Object.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:59:12)', moduleId: './js/eslint.js', moduleTrace: [], details: undefined, stack: 'ModuleBuildError: Module build failed (from ../../home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js):\n' + "Error: Cannot find module '`@babel`/plugin-syntax-dynamic-import'\n" + 'Require stack:\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/index.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/index.js\n' + '- /home/hugo/workspace-os/webpack-encore/lib/config/parse-runtime.js\n' + '- /home/hugo/workspace-os/webpack-encore/test/helpers/setup.js\n' + '- /home/hugo/workspace-os/webpack-encore/test/bin/encore.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/esm-utils.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/mocha.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/cli/one-and-dones.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/lib/cli/options.js\n' + '- /home/hugo/workspace-os/webpack-encore/node_modules/mocha/bin/mocha\n' + ' at Function.Module._resolveFilename (internal/modules/cjs/loader.js:831:15)\n' + ' at resolve (internal/modules/cjs/helpers.js:80:19)\n' + ' at resolveStandardizedName (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js:96:7)\n' + ' at resolvePlugin (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js:40:10)\n' + ' at loadPlugin (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/files/plugins.js:48:20)\n' + ' at loadPlugin.next (<anonymous>)\n' + ' at createDescriptor (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:179:16)\n' + ' at createDescriptor.next (<anonymous>)\n' + ' at step (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:261:32)\n' + ' at evaluateAsync (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:291:5)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:44:11\n' + ' at Array.forEach (<anonymous>)\n' + ' at Function.async (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:43:15)\n' + ' at Function.all (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:216:13)\n' + ' at Generator.next (<anonymous>)\n' + ' at createDescriptors (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:134:38)\n' + ' at createDescriptors.next (<anonymous>)\n' + ' at createPluginDescriptors (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:130:17)\n' + ' at createPluginDescriptors.next (<anonymous>)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-descriptors.js:86:32\n' + ' at Generator.next (<anonymous>)\n' + ' at Function.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/gensync-utils/async.js:16:3)\n' + ' at Generator.next (<anonymous>)\n' + ' at step (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:269:25)\n' + ' at evaluateAsync (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:291:5)\n' + ' at Function.errback (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:113:7)\n' + ' at errback (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/gensync-utils/async.js:60:18)\n' + ' at async (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:188:31)\n' + ' at onFirstPause (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:216:13)\n' + ' at Generator.next (<anonymous>)\n' + ' at cachedFunction (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/caching.js:58:46)\n' + ' at cachedFunction.next (<anonymous>)\n' + ' at mergeChainOpts (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-chain.js:405:34)\n' + ' at mergeChainOpts.next (<anonymous>)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-chain.js:364:14\n' + ' at Generator.next (<anonymous>)\n' + ' at buildRootChain (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/config-chain.js:57:36)\n' + ' at buildRootChain.next (<anonymous>)\n' + ' at loadPrivatePartialConfig (/home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/partial.js:85:62)\n' + ' at loadPrivatePartialConfig.next (<anonymous>)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/`@babel`/core/lib/config/partial.js:131:25\n' + ' at Generator.next (<anonymous>)\n' + ' at step (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:269:25)\n' + ' at evaluateAsync (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:291:5)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:93:9\n' + ' at new Promise (<anonymous>)\n' + ' at async (/home/hugo/workspace-os/webpack-encore/node_modules/gensync/index.js:92:14)\n' + ' at Object.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:155:26)\n' + ' at Generator.next (<anonymous>)\n' + ' at asyncGeneratorStep (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:3:103)\n' + ' at _next (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:5:194)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:5:364\n' + ' at new Promise (<anonymous>)\n' + ' at Object.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:5:97)\n' + ' at Object.loader (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:64:18)\n' + ' at Object.<anonymous> (/home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:59:12)\n' + ' at processResult (/home/hugo/workspace-os/webpack-encore/node_modules/webpack/lib/NormalModule.js:676:19)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/webpack/lib/NormalModule.js:778:5\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/loader-runner/lib/LoaderRunner.js:399:11\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/loader-runner/lib/LoaderRunner.js:251:18\n' + ' at context.callback (/home/hugo/workspace-os/webpack-encore/node_modules/loader-runner/lib/LoaderRunner.js:124:13)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/babel-loader/lib/index.js:59:103' }, { message: 'No ESLint configuration found in /tmp/err9jr/js.', details: undefined, stack: 'Error: No ESLint configuration found in /tmp/err9jr/js.\n' + ' at CascadingConfigArrayFactory._finalizeConfigArray (/home/hugo/workspace-os/webpack-encore/node_modules/`@eslint`/eslintrc/lib/cascading-config-array-factory.js:508:19)\n' + ' at CascadingConfigArrayFactory.getConfigArrayForFile (/home/hugo/workspace-os/webpack-encore/node_modules/`@eslint`/eslintrc/lib/cascading-config-array-factory.js:299:21)\n' + ' at FileEnumerator._iterateFilesWithFile (/home/hugo/workspace-os/webpack-encore/node_modules/eslint/lib/cli-engine/file-enumerator.js:365:43)\n' + ' at FileEnumerator._iterateFiles (/home/hugo/workspace-os/webpack-encore/node_modules/eslint/lib/cli-engine/file-enumerator.js:346:25)\n' + ' at FileEnumerator.iterateFiles (/home/hugo/workspace-os/webpack-encore/node_modules/eslint/lib/cli-engine/file-enumerator.js:296:59)\n' + ' at iterateFiles.next (<anonymous>)\n' + ' at CLIEngine.executeOnFiles (/home/hugo/workspace-os/webpack-encore/node_modules/eslint/lib/cli-engine/cli-engine.js:765:48)\n' + ' at ESLint.lintFiles (/home/hugo/workspace-os/webpack-encore/node_modules/eslint/lib/eslint/eslint.js:530:23)\n' + ' at lintFiles (/home/hugo/workspace-os/webpack-encore/node_modules/eslint-webpack-plugin/dist/getESLint.js:57:36)\n' + ' at lint (/home/hugo/workspace-os/webpack-encore/node_modules/eslint-webpack-plugin/dist/linter.js:93:21)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/eslint-webpack-plugin/dist/index.js:138:11\n' + ' at Hook.eval [as callAsync] (eval at create (/home/hugo/workspace-os/webpack-encore/node_modules/webpack/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:16:1)\n' + ' at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/home/hugo/workspace-os/webpack-encore/node_modules/webpack/node_modules/tapable/lib/Hook.js:18:14)\n' + ' at Compilation.finish (/home/hugo/workspace-os/webpack-encore/node_modules/webpack/lib/Compilation.js:2155:28)\n' + ' at /home/hugo/workspace-os/webpack-encore/node_modules/webpack/lib/Compiler.js:1075:19\n' + ' at processTicksAndRejections (internal/process/task_queues.js:79:11)' } ] 2) Code splitting with dynamic import ERROR Failed to compile with 1 errors 15:54:37 error in ./test_tmp/tsnh3r/js/code_splitting_dynamic_import.js 15:54:37 ``` ~The dependency ``@babel`/plugin-syntax-dynamic-import` is present in Encore's `package.json`, so I don't really understand the issue... 😬~ ~Any help would be appreciated! :heart:~ **EDIT:** fixed, see #985 (comment) WDYT? Thanks! Commits ------- 8c33cb1 Move from eslint-loader to eslint-webpack-plugin, close #847
2 parents fbb0bb6 + 8c33cb1 commit ba177af

File tree

13 files changed

+504
-38
lines changed

13 files changed

+504
-38
lines changed

index.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,7 @@ class Encore {
12691269
* Configure the loader to lint `.vue` files
12701270
* ```
12711271
*
1272+
* @deprecated Prefer using "Encore.enableEslintPlugin()" instead.
12721273
* @param {string|object|function} eslintLoaderOptionsOrCallback
12731274
* @param {object} encoreOptions
12741275
* @returns {Encore}
@@ -1279,6 +1280,38 @@ class Encore {
12791280
return this;
12801281
}
12811282

1283+
/**
1284+
* If enabled, the eslint-webpack-plugin is enabled.
1285+
*
1286+
* https://github.com/webpack-contrib/eslint-webpack-plugin
1287+
*
1288+
* ```
1289+
* // enables the eslint plugin using the default eslint configuration.
1290+
* Encore.enableEslintPlugin();
1291+
*
1292+
* // You can also pass in an object of options
1293+
* // that will be passed on to the eslint-webpack-plugin
1294+
* Encore.enableEslintPlugin({
1295+
* emitWarning: false
1296+
* });
1297+
*
1298+
* // For a more advanced usage you can pass in a callback
1299+
* // https://github.com/webpack-contrib/eslint-webpack-plugin#options
1300+
* Encore.enableEslintPlugin((options) => {
1301+
* options.extensions.push('vue'); // to lint Vue files
1302+
* options.emitWarning = false;
1303+
* });
1304+
* ```
1305+
*
1306+
* @param {string|object|function} eslintPluginOptionsOrCallback
1307+
* @returns {Encore}
1308+
*/
1309+
enableEslintPlugin(eslintPluginOptionsOrCallback = () => {}) {
1310+
webpackConfig.enableEslintPlugin(eslintPluginOptionsOrCallback);
1311+
1312+
return this;
1313+
}
1314+
12821315
/**
12831316
* If enabled, display build notifications using
12841317
* webpack-notifier.

lib/WebpackConfig.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ class WebpackConfig {
120120
this.usePreact = false;
121121
this.useVueLoader = false;
122122
this.useEslintLoader = false;
123+
this.useEslintPlugin = false;
123124
this.useTypeScriptLoader = false;
124125
this.useForkedTypeScriptTypeChecking = false;
125126
this.useBabelTypeScriptPreset = false;
@@ -171,6 +172,7 @@ class WebpackConfig {
171172
this.devServerOptionsConfigurationCallback = () => {};
172173
this.vueLoaderOptionsCallback = () => {};
173174
this.eslintLoaderOptionsCallback = () => {};
175+
this.eslintPluginOptionsCallback = () => {};
174176
this.tsConfigurationCallback = () => {};
175177
this.handlebarsConfigurationCallback = () => {};
176178
this.miniCssExtractLoaderConfigurationCallback = () => {};
@@ -803,6 +805,12 @@ class WebpackConfig {
803805
}
804806

805807
enableEslintLoader(eslintLoaderOptionsOrCallback = () => {}, eslintOptions = {}) {
808+
logger.deprecation('Encore.enableEslintLoader() is deprecated, please use Encore.enableEslintPlugin() instead.');
809+
810+
if (this.useEslintPlugin) {
811+
throw new Error('Encore.enableEslintLoader() can not be called when Encore.enableEslintPlugin() has been called.');
812+
}
813+
806814
this.useEslintLoader = true;
807815

808816
if (typeof eslintLoaderOptionsOrCallback === 'function') {
@@ -831,6 +839,24 @@ class WebpackConfig {
831839
this.eslintOptions = eslintOptions;
832840
}
833841

842+
enableEslintPlugin(eslintPluginOptionsOrCallback = () => {}) {
843+
if (this.useEslintLoader) {
844+
throw new Error('Encore.enableEslintPlugin() can not be called when Encore.enableEslintLoader() has been called.');
845+
}
846+
847+
this.useEslintPlugin = true;
848+
849+
if (typeof eslintPluginOptionsOrCallback === 'function') {
850+
this.eslintPluginOptionsCallback = eslintPluginOptionsOrCallback;
851+
} else if (typeof eslintPluginOptionsOrCallback === 'object') {
852+
this.eslintPluginOptionsCallback = (options) => {
853+
Object.assign(options, eslintPluginOptionsOrCallback);
854+
};
855+
} else {
856+
throw new Error('Argument 1 to enableEslintPlugin() must be either an object or callback function.');
857+
}
858+
}
859+
834860
enableBuildNotifications(enabled = true, notifierPluginOptionsCallback = () => {}) {
835861
if (typeof notifierPluginOptionsCallback !== 'function') {
836862
throw new Error('Argument 2 to enableBuildNotifications() must be a callback function.');

lib/config-generator.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const vuePluginUtil = require('./plugins/vue');
3737
const friendlyErrorPluginUtil = require('./plugins/friendly-errors');
3838
const assetOutputDisplay = require('./plugins/asset-output-display');
3939
const notifierPluginUtil = require('./plugins/notifier');
40+
const eslintPluginUtil = require('./plugins/eslint');
4041
const PluginPriorities = require('./plugins/plugin-priorities');
4142
const applyOptionsCallback = require('./utils/apply-options-callback');
4243
const copyEntryTmpName = require('./utils/copyEntryTmpName');
@@ -454,6 +455,8 @@ class ConfigGenerator {
454455

455456
vuePluginUtil(plugins, this.webpackConfig);
456457

458+
eslintPluginUtil(plugins, this.webpackConfig);
459+
457460
if (!this.webpackConfig.runtimeConfig.outputJson) {
458461
const friendlyErrorPlugin = friendlyErrorPluginUtil(this.webpackConfig);
459462
plugins.push({

lib/features.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ const features = {
126126
],
127127
description: 'Enable ESLint checks'
128128
},
129+
eslint_plugin: {
130+
method: 'enableEslintPlugin()',
131+
// eslint is needed so the end-user can do things
132+
packages: [
133+
{ name: 'eslint' },
134+
{ name: 'eslint-webpack-plugin', enforce_version: true },
135+
],
136+
description: 'Enable ESLint checks'
137+
},
129138
copy_files: {
130139
method: 'copyFiles()',
131140
packages: [

lib/loaders/eslint.js

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,12 @@
99

1010
'use strict';
1111

12+
const forceSync = require('sync-rpc');
13+
const hasEslintConfiguration = forceSync(require.resolve('../utils/has-eslint-configuration'));
1214
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
1315
const loaderFeatures = require('../features');
1416
const applyOptionsCallback = require('../utils/apply-options-callback');
1517

16-
function isMissingConfigError(e) {
17-
if (!e.message || !e.message.includes('No ESLint configuration found')) {
18-
return false;
19-
}
20-
21-
return true;
22-
}
23-
2418
module.exports = {
2519
/**
2620
* @param {WebpackConfig} webpackConfig
@@ -29,19 +23,11 @@ module.exports = {
2923
getOptions(webpackConfig) {
3024
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('eslint');
3125

32-
const eslint = require('eslint'); // eslint-disable-line node/no-unpublished-require
33-
const engine = new eslint.CLIEngine({
34-
cwd: webpackConfig.runtimeConfig.context,
35-
});
36-
37-
try {
38-
engine.getConfigForFile('webpack.config.js');
39-
} catch (e) {
40-
if (isMissingConfigError(e)) {
41-
const chalk = require('chalk');
42-
const packageHelper = require('../package-helper');
26+
if (!hasEslintConfiguration(webpackConfig)) {
27+
const chalk = require('chalk');
28+
const packageHelper = require('../package-helper');
4329

44-
const message = `No ESLint configration has been found.
30+
const message = `No ESLint configuration has been found.
4531
4632
${chalk.bgGreen.black('', 'FIX', '')} Run command ${chalk.yellow('./node_modules/.bin/eslint --init')} or manually create a ${chalk.yellow('.eslintrc.js')} file at the root of your project.
4733
@@ -57,10 +43,7 @@ module.exports = {
5743
Install ${chalk.yellow('babel-eslint')} to prevent potential parsing issues: ${packageHelper.getInstallCommand([[{ name: 'babel-eslint' }]])}
5844
5945
`;
60-
throw new Error(message);
61-
}
62-
63-
throw e;
46+
throw new Error(message);
6447
}
6548

6649
const eslintLoaderOptions = {

lib/plugins/eslint.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* This file is part of the Symfony Webpack Encore package.
3+
*
4+
* (c) Fabien Potencier <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
const forceSync = require('sync-rpc');
13+
const hasEslintConfiguration = forceSync(require.resolve('../utils/has-eslint-configuration'));
14+
const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unused-vars
15+
const EslintPlugin = require('eslint-webpack-plugin'); //eslint-disable-line node/no-unpublished-require
16+
const applyOptionsCallback = require('../utils/apply-options-callback');
17+
const pluginFeatures = require('../features');
18+
19+
/**
20+
* Support for ESLint.
21+
*
22+
* @param {Array} plugins
23+
* @param {WebpackConfig} webpackConfig
24+
* @return {void}
25+
*/
26+
module.exports = function(plugins, webpackConfig) {
27+
if (webpackConfig.useEslintPlugin) {
28+
pluginFeatures.ensurePackagesExistAndAreCorrectVersion('eslint_plugin');
29+
30+
if (!hasEslintConfiguration(webpackConfig)) {
31+
const chalk = require('chalk');
32+
const packageHelper = require('../package-helper');
33+
34+
const message = `No ESLint configuration has been found.
35+
36+
${chalk.bgGreen.black('', 'FIX', '')} Run command ${chalk.yellow('./node_modules/.bin/eslint --init')} or manually create a ${chalk.yellow('.eslintrc.js')} file at the root of your project.
37+
38+
If you prefer to create a ${chalk.yellow('.eslintrc.js')} file by yourself, here is an example to get you started:
39+
40+
${chalk.yellow(`// .eslintrc.js
41+
module.exports = {
42+
parser: 'babel-eslint',
43+
extends: ['eslint:recommended'],
44+
}
45+
`)}
46+
47+
Install ${chalk.yellow('babel-eslint')} to prevent potential parsing issues: ${packageHelper.getInstallCommand([[{ name: 'babel-eslint' }]])}
48+
49+
`;
50+
throw new Error(message);
51+
}
52+
53+
const eslintPluginOptions = {
54+
emitWarning: true,
55+
extensions: ['js', 'jsx'],
56+
};
57+
58+
plugins.push({
59+
plugin: new EslintPlugin(
60+
applyOptionsCallback(webpackConfig.eslintPluginOptionsCallback, eslintPluginOptions)
61+
),
62+
});
63+
}
64+
};

lib/utils/has-eslint-configuration.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* This file is part of the Symfony Webpack Encore package.
3+
*
4+
* (c) Fabien Potencier <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
'use strict';
11+
12+
13+
function isMissingConfigError(e) {
14+
if (!e.message || !e.message.includes('No ESLint configuration found')) {
15+
return false;
16+
}
17+
18+
return true;
19+
}
20+
21+
/**
22+
* @returns {Promise<boolean>}
23+
*/
24+
module.exports = async function() {
25+
/**
26+
* @param {WebpackConfig} webpackConfig
27+
* @returns {Promise<boolean>}
28+
*/
29+
return async function(webpackConfig) {
30+
const { ESLint } = require('eslint'); // eslint-disable-line node/no-unpublished-require
31+
const eslint = new ESLint({
32+
cwd: webpackConfig.runtimeConfig.context,
33+
});
34+
35+
try {
36+
await eslint.calculateConfigForFile('webpack.config.js');
37+
} catch (e) {
38+
return !isMissingConfigError(e);
39+
}
40+
41+
return true;
42+
};
43+
};

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"resolve-url-loader": "^3.1.2",
4646
"semver": "^7.3.2",
4747
"style-loader": "^2.0.0",
48+
"sync-rpc": "^1.3.6",
4849
"terser-webpack-plugin": "^5.1.1",
4950
"tmp": "^0.2.1",
5051
"webpack": "^5.35",
@@ -69,11 +70,12 @@
6970
"chai-fs": "^2.0.0",
7071
"chai-subset": "^1.6.0",
7172
"core-js": "^3.0.0",
72-
"eslint": "^6.7.0 || ^7.0.0",
73+
"eslint": "^7.0.0",
7374
"eslint-loader": "^4.0.0",
7475
"eslint-plugin-header": "^3.0.0",
7576
"eslint-plugin-import": "^2.8.0",
7677
"eslint-plugin-node": "^11.1.0",
78+
"eslint-webpack-plugin": "^2.5.4",
7779
"file-loader": "^6.0.0",
7880
"fork-ts-checker-webpack-plugin": "^5.0.0 || ^6.0.0",
7981
"fs-extra": "^9.0.0",

test/WebpackConfig.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,5 +1512,24 @@ describe('WebpackConfig object', () => {
15121512
});
15131513
}).to.throw('"notExisting" is not a valid key for enableEslintLoader(). Valid keys: lintVue.');
15141514
});
1515+
1516+
it('ESLint loader can not be enabled if ESLint Webpack Plugin is already enabled', () => {
1517+
const config = createConfig();
1518+
config.enableEslintPlugin();
1519+
1520+
expect(function() {
1521+
config.enableEslintLoader();
1522+
}).to.throw('Encore.enableEslintLoader() can not be called when Encore.enableEslintPlugin() has been called.');
1523+
});
1524+
});
1525+
describe('enableEslintPlugin', () => {
1526+
it('ESLint loader can not be enabled if ESLint Webpack Plugin is already enabled', () => {
1527+
const config = createConfig();
1528+
config.enableEslintLoader();
1529+
1530+
expect(function() {
1531+
config.enableEslintPlugin();
1532+
}).to.throw('Encore.enableEslintPlugin() can not be called when Encore.enableEslintLoader() has been called.');
1533+
});
15151534
});
15161535
});

0 commit comments

Comments
 (0)