Skip to content

Commit 829d84a

Browse files
committed
feature #687 Remove ESLint user-related config (Kocal, weaverryan)
This PR was merged into the master branch. Discussion ---------- Remove ESLint user-related config Hi ✋ This PR is a proposal for #657 implementation and should fix issues encountered in #473, #574, #656, #659, and #685. As discussed in #657, it would be better if Encore didn't configure ESLint itself. It should be done by the end user in an configuration file (e.g.: `.eslintrc.js`) ESLint's `parser` option is not configured by default anymore, and `babel-eslint` requirement has been removed. We can considering this as a breaking change, end users should now configure `parser: 'babel-eslint` themselves. Method `Encore.enableEslintLoader('extends-name')` has been deprecated and undocumented, in order to prevent end users to configure ESLint through Encore. A nice message is also displayed when no ESLint configuration is found: ![Capture d’écran de 2020-01-12 11-15-09](https://user-images.githubusercontent.com/2103975/72217430-dfa2a480-352d-11ea-96e3-0e77236127d6.png) I couldn't use `FriendlyErrorsPlugin` for this because error is not coming from Webpack. If someone has a better idea... 😕 **Pros:** - end users have now full control hover ESLint configuration **by default** - no need to `delete options.parser` when trying to use [`eslint-plugin-vue`](https://github.com/vuejs/eslint-plugin-vue) or [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser) - IDEs will be able to integrate ESLint (if they support it) **Cons:** - end users should now configure `parser` option to `babel-eslint` themselves - end users will have to move their config to external config file, but it's ok What do you think? Thanks. **EDIT:** tests failures are unrelated I think. Commits ------- 9d3b02f tweaking wording and order d23982a Display message if no ESLint configuration is found c828b32 Deprecate `Encore.enableEslintLoader('extends-name')` 9f31d95 Add test for ESLint with external configuration (and babel-eslint usage) 3ba6cf2 Remove babel-eslint from ESLint configuration
2 parents 28086b1 + 9d3b02f commit 829d84a

File tree

10 files changed

+123
-12
lines changed

10 files changed

+123
-12
lines changed

fixtures/js/eslint-es2018.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const a = { x: 0, y: 1, z: 2 };
2+
const { x, ...b } = a;

index.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,18 +1070,14 @@ class Encore {
10701070
* // enables the eslint loaded using the default eslint configuration.
10711071
* Encore.enableEslintLoader();
10721072
*
1073-
* // Optionally, you can pass in the configuration eslint should extend.
1074-
* Encore.enableEslintLoader('airbnb');
1075-
*
10761073
* // You can also pass in an object of options
10771074
* // that will be passed on to the eslint-loader
10781075
* Encore.enableEslintLoader({
1079-
* extends: 'airbnb',
10801076
* emitWarning: false
10811077
* });
10821078
*
10831079
* // For a more advanced usage you can pass in a callback
1084-
* // https://github.com/MoOx/eslint-loader#options
1080+
* // https://github.com/webpack-contrib/eslint-loader#options
10851081
* Encore.enableEslintLoader((options) => {
10861082
* options.extends = 'airbnb';
10871083
* options.emitWarning = false;

lib/WebpackConfig.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,8 @@ class WebpackConfig {
686686
}
687687

688688
if (typeof eslintLoaderOptionsOrCallback === 'string') {
689+
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.');
690+
689691
this.eslintLoaderOptionsCallback = (options) => {
690692
options.extends = eslintLoaderOptionsOrCallback;
691693
};

lib/features.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ const features = {
103103
packages: [
104104
{ name: 'eslint' },
105105
{ name: 'eslint-loader', enforce_version: true },
106-
{ name: 'babel-eslint', enforce_version: true },
107106
],
108107
description: 'Enable ESLint checks'
109108
},

lib/loaders/eslint.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ const WebpackConfig = require('../WebpackConfig'); //eslint-disable-line no-unus
1313
const loaderFeatures = require('../features');
1414
const applyOptionsCallback = require('../utils/apply-options-callback');
1515

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+
1624
module.exports = {
1725
/**
1826
* @param {WebpackConfig} webpackConfig
@@ -21,9 +29,42 @@ module.exports = {
2129
getOptions(webpackConfig) {
2230
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('eslint');
2331

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.config.getConfigHierarchy(webpackConfig.runtimeConfig.context);
39+
} catch (e) {
40+
if (isMissingConfigError(e)) {
41+
const chalk = require('chalk').default;
42+
const packageHelper = require('../package-helper');
43+
44+
const message = `No ESLint configration has been found.
45+
46+
${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.
47+
48+
If you prefer to create a ${chalk.yellow('.eslintrc.js')} file by yourself, here is an example to get you started:
49+
50+
${chalk.yellow(`// .eslintrc.js
51+
module.exports = {
52+
parser: 'babel-eslint',
53+
extends: ['eslint:recommended'],
54+
}
55+
`)}
56+
57+
Install ${chalk.yellow('babel-eslint')} to prevent potential parsing issues: ${packageHelper.getInstallCommand([[{ name: 'babel-eslint' }]])}
58+
59+
`;
60+
throw new Error(message);
61+
}
62+
63+
throw e;
64+
}
65+
2466
const eslintLoaderOptions = {
2567
cache: true,
26-
parser: 'babel-eslint',
2768
emitWarning: true
2869
};
2970

lib/package-helper.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,5 @@ module.exports = {
195195
getMissingPackageRecommendations,
196196
getInvalidPackageVersionRecommendations,
197197
addPackagesVersionConstraint,
198+
getInstallCommand,
198199
};

test/config-generator.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@ describe('The config-generator function', () => {
372372
});
373373

374374
it('enableEslintLoader("extends-name")', () => {
375+
before(() => {
376+
logger.reset();
377+
});
378+
375379
const config = createConfig();
376380
config.addEntry('main', './main');
377381
config.publicPath = '/';
@@ -380,6 +384,7 @@ describe('The config-generator function', () => {
380384

381385
const actualConfig = configGenerator(config);
382386

387+
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.');
383388
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
384389
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
385390
});

test/functional.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
'use strict';
1111

12+
const os = require('os');
1213
const chai = require('chai');
1314
chai.use(require('chai-fs'));
1415
const expect = chai.expect;
@@ -1721,6 +1722,73 @@ module.exports = {
17211722
}, true);
17221723
});
17231724

1725+
it('When enabled, eslint checks for linting errors by using configuration from file', (done) => {
1726+
const cwd = process.cwd();
1727+
after(() => {
1728+
process.chdir(cwd);
1729+
});
1730+
1731+
const appDir = testSetup.createTestAppDir();
1732+
const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev');
1733+
config.setPublicPath('/build');
1734+
config.addEntry('main', './js/eslint-es2018');
1735+
config.enableEslintLoader({
1736+
// Force eslint-loader to output errors instead of sometimes
1737+
// using warnings (see: https://github.com/MoOx/eslint-loader#errors-and-warning)
1738+
emitError: true,
1739+
});
1740+
fs.writeFileSync(
1741+
path.join(appDir, '.eslintrc.js'),
1742+
`
1743+
module.exports = {
1744+
parser: 'babel-eslint',
1745+
rules: {
1746+
'indent': ['error', 2],
1747+
'no-unused-vars': ['error', { 'args': 'all' }]
1748+
}
1749+
} `
1750+
);
1751+
1752+
process.chdir(appDir);
1753+
1754+
testSetup.runWebpack(config, (webpackAssert, stats) => {
1755+
const eslintErrors = stats.toJson().errors[0];
1756+
1757+
expect(eslintErrors).not.to.contain('Parsing error: Unexpected token ..');
1758+
expect(eslintErrors).to.contain('Expected indentation of 0 spaces but found 2');
1759+
expect(eslintErrors).to.contain('\'x\' is assigned a value but never used');
1760+
expect(eslintErrors).to.contain('\'b\' is assigned a value but never used');
1761+
1762+
done();
1763+
}, true);
1764+
});
1765+
1766+
it('When enabled and without any configuration, ESLint will throw an error and a nice message should be displayed', (done) => {
1767+
const cwd = process.cwd();
1768+
1769+
this.timeout(5000);
1770+
setTimeout(() => {
1771+
process.chdir(cwd);
1772+
done();
1773+
}, 4000);
1774+
1775+
const appDir = testSetup.createTestAppDir(os.tmpdir()); // to prevent issue with Encore's .eslintrc.js
1776+
const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev');
1777+
config.setPublicPath('/build');
1778+
config.addEntry('main', './js/eslint');
1779+
config.enableEslintLoader({
1780+
// Force eslint-loader to output errors instead of sometimes
1781+
// using warnings (see: https://github.com/MoOx/eslint-loader#errors-and-warning)
1782+
emitError: true,
1783+
});
1784+
1785+
process.chdir(appDir);
1786+
1787+
expect(() => {
1788+
testSetup.runWebpack(config, (webpackAssert, stats) => {});
1789+
}).to.throw('No ESLint configration has been found.');
1790+
});
1791+
17241792
it('Code splitting with dynamic import', (done) => {
17251793
const config = createWebpackConfig('www/build', 'dev');
17261794
config.setPublicPath('/build');

test/helpers/setup.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ const testFixturesDir = path.join(__dirname, '../', '../', 'fixtures');
2525

2626
let servers = [];
2727

28-
function createTestAppDir() {
29-
const testAppDir = path.join(tmpDir, Math.random().toString(36).substring(7));
28+
function createTestAppDir(rootDir = tmpDir) {
29+
const testAppDir = path.join(rootDir, Math.random().toString(36).substring(7));
3030

3131
// copy the fixtures into this new directory
3232
fs.copySync(testFixturesDir, testAppDir);

test/loaders/eslint.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ describe('loaders/eslint', () => {
3030

3131
expect(actualOptions).to.deep.equal({
3232
cache: true,
33-
parser: 'babel-eslint',
3433
emitWarning: true
3534
});
3635
});
@@ -45,7 +44,6 @@ describe('loaders/eslint', () => {
4544

4645
expect(actualOptions).to.deep.equal({
4746
cache: true,
48-
parser: 'babel-eslint',
4947
emitWarning: true,
5048
extends: 'airbnb'
5149
});
@@ -61,7 +59,6 @@ describe('loaders/eslint', () => {
6159

6260
expect(actualOptions).to.deep.equal({
6361
cache: true,
64-
parser: 'babel-eslint',
6562
emitWarning: false
6663
});
6764
});

0 commit comments

Comments
 (0)