diff --git a/fixtures/js/eslint-es2018.js b/fixtures/js/eslint-es2018.js new file mode 100644 index 00000000..0d069ab1 --- /dev/null +++ b/fixtures/js/eslint-es2018.js @@ -0,0 +1,2 @@ + const a = { x: 0, y: 1, z: 2 }; + const { x, ...b } = a; diff --git a/index.js b/index.js index fe24f50f..edb2debe 100644 --- a/index.js +++ b/index.js @@ -1070,18 +1070,14 @@ class Encore { * // enables the eslint loaded using the default eslint configuration. * Encore.enableEslintLoader(); * - * // Optionally, you can pass in the configuration eslint should extend. - * Encore.enableEslintLoader('airbnb'); - * * // You can also pass in an object of options * // that will be passed on to the eslint-loader * Encore.enableEslintLoader({ - * extends: 'airbnb', * emitWarning: false * }); * * // For a more advanced usage you can pass in a callback - * // https://github.com/MoOx/eslint-loader#options + * // https://github.com/webpack-contrib/eslint-loader#options * Encore.enableEslintLoader((options) => { * options.extends = 'airbnb'; * options.emitWarning = false; diff --git a/lib/WebpackConfig.js b/lib/WebpackConfig.js index 64b7dc33..46e013a4 100644 --- a/lib/WebpackConfig.js +++ b/lib/WebpackConfig.js @@ -686,6 +686,8 @@ class WebpackConfig { } if (typeof eslintLoaderOptionsOrCallback === 'string') { + logger.deprecation('enableEslintLoader: Extending from a configuration is deprecated, please use a configuration file instead. See https://eslint.org/docs/user-guide/configuring for more information.'); + this.eslintLoaderOptionsCallback = (options) => { options.extends = eslintLoaderOptionsOrCallback; }; diff --git a/lib/features.js b/lib/features.js index e5686c20..d8a27fe1 100644 --- a/lib/features.js +++ b/lib/features.js @@ -103,7 +103,6 @@ const features = { packages: [ { name: 'eslint' }, { name: 'eslint-loader', enforce_version: true }, - { name: 'babel-eslint', enforce_version: true }, ], description: 'Enable ESLint checks' }, diff --git a/lib/loaders/eslint.js b/lib/loaders/eslint.js index f1e6f8cf..e96c7788 100644 --- a/lib/loaders/eslint.js +++ b/lib/loaders/eslint.js @@ -13,6 +13,14 @@ const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unus const loaderFeatures = require('../features'); const applyOptionsCallback = require('../utils/apply-options-callback'); +function isMissingConfigError(e) { + if (!e.message || !e.message.includes('No ESLint configuration found')) { + return false; + } + + return true; +} + module.exports = { /** * @param {WebpackConfig} webpackConfig @@ -21,9 +29,42 @@ module.exports = { getOptions(webpackConfig) { loaderFeatures.ensurePackagesExistAndAreCorrectVersion('eslint'); + const eslint = require('eslint'); // eslint-disable-line node/no-unpublished-require + const engine = new eslint.CLIEngine({ + cwd: webpackConfig.runtimeConfig.context, + }); + + try { + engine.config.getConfigHierarchy(webpackConfig.runtimeConfig.context); + } catch (e) { + if (isMissingConfigError(e)) { + const chalk = require('chalk').default; + const packageHelper = require('../package-helper'); + + const message = `No ESLint configration has been found. + +${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. + +If you prefer to create a ${chalk.yellow('.eslintrc.js')} file by yourself, here is an example to get you started: + +${chalk.yellow(`// .eslintrc.js +module.exports = { + parser: 'babel-eslint', + extends: ['eslint:recommended'], +} +`)} + +Install ${chalk.yellow('babel-eslint')} to prevent potential parsing issues: ${packageHelper.getInstallCommand([[{ name: 'babel-eslint' }]])} + +`; + throw new Error(message); + } + + throw e; + } + const eslintLoaderOptions = { cache: true, - parser: 'babel-eslint', emitWarning: true }; diff --git a/lib/package-helper.js b/lib/package-helper.js index 0af4023c..e6c3d7a8 100644 --- a/lib/package-helper.js +++ b/lib/package-helper.js @@ -195,4 +195,5 @@ module.exports = { getMissingPackageRecommendations, getInvalidPackageVersionRecommendations, addPackagesVersionConstraint, + getInstallCommand, }; diff --git a/test/config-generator.js b/test/config-generator.js index 4f2e5767..ed8bfa09 100644 --- a/test/config-generator.js +++ b/test/config-generator.js @@ -372,6 +372,10 @@ describe('The config-generator function', () => { }); it('enableEslintLoader("extends-name")', () => { + before(() => { + logger.reset(); + }); + const config = createConfig(); config.addEntry('main', './main'); config.publicPath = '/'; @@ -380,6 +384,7 @@ describe('The config-generator function', () => { const actualConfig = configGenerator(config); + expect(JSON.stringify(logger.getMessages().deprecation)).to.contain('enableEslintLoader: Extending from a configuration is deprecated, please use a configuration file instead. See https://eslint.org/docs/user-guide/configuring for more information.'); expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader'); expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name'); }); diff --git a/test/functional.js b/test/functional.js index 1d88d42f..9ade1e2f 100644 --- a/test/functional.js +++ b/test/functional.js @@ -9,6 +9,7 @@ 'use strict'; +const os = require('os'); const chai = require('chai'); chai.use(require('chai-fs')); const expect = chai.expect; @@ -1722,6 +1723,73 @@ module.exports = { }, true); }); + it('When enabled, eslint checks for linting errors by using configuration from file', (done) => { + const cwd = process.cwd(); + after(() => { + process.chdir(cwd); + }); + + const appDir = testSetup.createTestAppDir(); + const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev'); + config.setPublicPath('/build'); + config.addEntry('main', './js/eslint-es2018'); + config.enableEslintLoader({ + // Force eslint-loader to output errors instead of sometimes + // using warnings (see: https://github.com/MoOx/eslint-loader#errors-and-warning) + emitError: true, + }); + fs.writeFileSync( + path.join(appDir, '.eslintrc.js'), + ` +module.exports = { + parser: 'babel-eslint', + rules: { + 'indent': ['error', 2], + 'no-unused-vars': ['error', { 'args': 'all' }] + } +} ` + ); + + process.chdir(appDir); + + testSetup.runWebpack(config, (webpackAssert, stats) => { + const eslintErrors = stats.toJson().errors[0]; + + expect(eslintErrors).not.to.contain('Parsing error: Unexpected token ..'); + expect(eslintErrors).to.contain('Expected indentation of 0 spaces but found 2'); + expect(eslintErrors).to.contain('\'x\' is assigned a value but never used'); + expect(eslintErrors).to.contain('\'b\' is assigned a value but never used'); + + done(); + }, true); + }); + + it('When enabled and without any configuration, ESLint will throw an error and a nice message should be displayed', (done) => { + const cwd = process.cwd(); + + this.timeout(5000); + setTimeout(() => { + process.chdir(cwd); + done(); + }, 4000); + + const appDir = testSetup.createTestAppDir(os.tmpdir()); // to prevent issue with Encore's .eslintrc.js + const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev'); + config.setPublicPath('/build'); + config.addEntry('main', './js/eslint'); + config.enableEslintLoader({ + // Force eslint-loader to output errors instead of sometimes + // using warnings (see: https://github.com/MoOx/eslint-loader#errors-and-warning) + emitError: true, + }); + + process.chdir(appDir); + + expect(() => { + testSetup.runWebpack(config, (webpackAssert, stats) => {}); + }).to.throw('No ESLint configration has been found.'); + }); + it('Code splitting with dynamic import', (done) => { const config = createWebpackConfig('www/build', 'dev'); config.setPublicPath('/build'); diff --git a/test/helpers/setup.js b/test/helpers/setup.js index 9cb61b64..4db5fd72 100644 --- a/test/helpers/setup.js +++ b/test/helpers/setup.js @@ -25,8 +25,8 @@ const testFixturesDir = path.join(__dirname, '../', '../', 'fixtures'); let servers = []; -function createTestAppDir() { - const testAppDir = path.join(tmpDir, Math.random().toString(36).substring(7)); +function createTestAppDir(rootDir = tmpDir) { + const testAppDir = path.join(rootDir, Math.random().toString(36).substring(7)); // copy the fixtures into this new directory fs.copySync(testFixturesDir, testAppDir); diff --git a/test/loaders/eslint.js b/test/loaders/eslint.js index 1160e326..570c13a9 100644 --- a/test/loaders/eslint.js +++ b/test/loaders/eslint.js @@ -30,7 +30,6 @@ describe('loaders/eslint', () => { expect(actualOptions).to.deep.equal({ cache: true, - parser: 'babel-eslint', emitWarning: true }); }); @@ -45,7 +44,6 @@ describe('loaders/eslint', () => { expect(actualOptions).to.deep.equal({ cache: true, - parser: 'babel-eslint', emitWarning: true, extends: 'airbnb' }); @@ -61,7 +59,6 @@ describe('loaders/eslint', () => { expect(actualOptions).to.deep.equal({ cache: true, - parser: 'babel-eslint', emitWarning: false }); });