Skip to content

Commit ce8c66d

Browse files
Jeroen MeeusLyrkan
Jeroen Meeus
authored andcommitted
#232 Added support for the eslint-loader
1 parent 92548f1 commit ce8c66d

File tree

9 files changed

+624
-150
lines changed

9 files changed

+624
-150
lines changed

index.js

+38-2
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,11 @@ class Encore {
288288
* than the DefinePlugin:
289289
*
290290
* const Encore = require('@symfony/webpack-encore');
291-
* const PluginPriorities = require('@symfony/webpack-encore/lib/plugins/plugin-priorities.js');
291+
* const PluginPriorities =
292+
* require('@symfony/webpack-encore/lib/plugins/plugin-priorities.js');
292293
*
293-
* Encore.addPlugin(new MyWebpackPlugin(), PluginPriorities.DefinePlugin);
294+
* Encore.addPlugin(new MyWebpackPlugin(),
295+
* PluginPriorities.DefinePlugin);
294296
*
295297
* @param {object} plugin
296298
* @param {number} priority
@@ -695,6 +697,40 @@ class Encore {
695697
return this;
696698
}
697699

700+
/**
701+
* If enabled, the eslint-loader is enabled.
702+
*
703+
* https://github.com/MoOx/eslint-loader
704+
*
705+
* // enables the eslint loaded using the default eslint configuration.
706+
* Encore.enableEslint();
707+
*
708+
* // Optionally, you can pass in the configuration eslint should extend.
709+
* Encore.enableEslint('airbnb');
710+
*
711+
* // You can also pass in an object of options
712+
* // that will be passed on to the eslint-loader
713+
* Encore.enableEslint({
714+
* extends: 'airbnb',
715+
emitWarning: false
716+
* });
717+
*
718+
* // For a more advanced usage you can pass in a callback
719+
* // https://github.com/MoOx/eslint-loader#options
720+
* Encore.enableEslint((options) => {
721+
* options.extends = 'airbnb';
722+
* options.emitWarning = fasle;
723+
* });
724+
*
725+
* @param {string|object|function} eslintLoaderOptionsOrCallback
726+
* @returns {Encore}
727+
*/
728+
enableEslintLoader(eslintLoaderOptionsOrCallback = () => {}) {
729+
webpackConfig.enableEslintLoader(eslintLoaderOptionsOrCallback);
730+
731+
return this;
732+
}
733+
698734
/**
699735
* If enabled, display build notifications using
700736
* webpack-notifier.

lib/WebpackConfig.js

+27
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class WebpackConfig {
6161
this.useReact = false;
6262
this.usePreact = false;
6363
this.useVueLoader = false;
64+
this.useEslintLoader = false;
6465
this.useTypeScriptLoader = false;
6566
this.useCoffeeScriptLoader = false;
6667
this.useForkedTypeScriptTypeChecking = false;
@@ -86,6 +87,7 @@ class WebpackConfig {
8687
this.stylusLoaderOptionsCallback = () => {};
8788
this.babelConfigurationCallback = () => {};
8889
this.vueLoaderOptionsCallback = () => {};
90+
this.eslintLoaderOptionsCallback = () => {};
8991
this.tsConfigurationCallback = () => {};
9092
this.coffeeScriptConfigurationCallback = () => {};
9193
this.handlebarsConfigurationCallback = () => {};
@@ -439,6 +441,31 @@ class WebpackConfig {
439441
this.vueLoaderOptionsCallback = vueLoaderOptionsCallback;
440442
}
441443

444+
enableEslintLoader(eslintLoaderOptionsOrCallback = () => {}) {
445+
this.useEslintLoader = true;
446+
447+
if (typeof eslintLoaderOptionsOrCallback === 'function') {
448+
this.eslintLoaderOptionsCallback = eslintLoaderOptionsOrCallback;
449+
return;
450+
}
451+
452+
if (typeof eslintLoaderOptionsOrCallback === 'string') {
453+
this.eslintLoaderOptionsCallback = (options) => {
454+
options.extends = eslintLoaderOptionsOrCallback;
455+
};
456+
return;
457+
}
458+
459+
if (typeof eslintLoaderOptionsOrCallback === 'object') {
460+
this.eslintLoaderOptionsCallback = (options) => {
461+
Object.assign(options, eslintLoaderOptionsOrCallback);
462+
};
463+
return;
464+
}
465+
466+
throw new Error('Argument 1 to enableEslintLoader() must be either a string, object or callback function.');
467+
}
468+
442469
enableBuildNotifications(enabled = true, notifierPluginOptionsCallback = () => {}) {
443470
if (typeof notifierPluginOptionsCallback !== 'function') {
444471
throw new Error('Argument 2 to enableBuildNotifications() must be a callback function.');

lib/config-generator.js

+11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const tsLoaderUtil = require('./loaders/typescript');
2222
const coffeeScriptLoaderUtil = require('./loaders/coffee-script');
2323
const vueLoaderUtil = require('./loaders/vue');
2424
const handlebarsLoaderUtil = require('./loaders/handlebars');
25+
const eslintLoaderUtil = require('./loaders/eslint');
2526
// plugins utils
2627
const extractTextPluginUtil = require('./plugins/extract-text');
2728
const deleteUnusedEntriesPluginUtil = require('./plugins/delete-unused-entries');
@@ -228,6 +229,16 @@ class ConfigGenerator {
228229
});
229230
}
230231

232+
if (this.webpackConfig.useEslintLoader) {
233+
rules.push({
234+
test: /\.jsx?$/,
235+
loader: 'eslint-loader',
236+
exclude: /node_modules/,
237+
enforce: 'pre',
238+
options: eslintLoaderUtil.getOptions(this.webpackConfig)
239+
});
240+
}
241+
231242
if (this.webpackConfig.useTypeScriptLoader) {
232243
rules.push({
233244
test: /\.tsx?$/,

lib/features.js

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ const features = {
6868
packages: ['vue', 'vue-loader', 'vue-template-compiler'],
6969
description: 'load VUE files'
7070
},
71+
eslint: {
72+
method: 'enableEslintLoader()',
73+
// eslint is needed so the end-user can do things
74+
packages: ['eslint', 'eslint-loader', 'babel-eslint', 'eslint-plugin-import'],
75+
description: 'load VUE files'
76+
},
7177
notifier: {
7278
method: 'enableBuildNotifications()',
7379
packages: ['webpack-notifier'],

lib/loaders/eslint.js

+43
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+
const loaderFeatures = require('../features');
13+
14+
/**
15+
* @param {WebpackConfig} webpackConfig
16+
* @return {Object} of options to use for eslint-loader options.
17+
*/
18+
module.exports = {
19+
getOptions(webpackConfig) {
20+
loaderFeatures.ensurePackagesExist('eslint');
21+
22+
const eslintLoaderOptions = {
23+
parser: 'babel-eslint',
24+
emitWarning: true,
25+
rules: {
26+
'linebreak-style': 'off'
27+
},
28+
'import/resolver': {
29+
webpack: {
30+
config: 'webpack.config.js'
31+
}
32+
}
33+
};
34+
35+
webpackConfig.eslintLoaderOptionsCallback.apply(
36+
// use eslintLoaderOptions as the this variable
37+
eslintLoaderOptions,
38+
[eslintLoaderOptions]
39+
);
40+
41+
return eslintLoaderOptions;
42+
}
43+
};

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,18 @@
5151
},
5252
"devDependencies": {
5353
"autoprefixer": "^6.7.7",
54+
"babel-eslint": "^8.2.1",
5455
"babel-plugin-transform-react-jsx": "^6.24.1",
5556
"babel-preset-es2015": "^6.24.1",
5657
"babel-preset-react": "^6.23.0",
5758
"chai": "^3.5.0",
5859
"chai-fs": "^1.0.0",
5960
"coffee-loader": "^0.9.0",
6061
"coffeescript": "^2.0.2",
61-
"eslint": "^3.19.0",
62+
"eslint": "^4.15.0",
63+
"eslint-loader": "^1.9.0",
6264
"eslint-plugin-header": "^1.0.0",
65+
"eslint-plugin-import": "^2.8.0",
6366
"eslint-plugin-node": "^4.2.2",
6467
"fork-ts-checker-webpack-plugin": "^0.2.7",
6568
"handlebars": "^4.0.11",

test/config-generator.js

+65
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,72 @@ describe('The config-generator function', () => {
377377

378378
expect(JSON.stringify(actualConfig.module.rules)).to.contain('handlebars-loader');
379379
});
380+
});
381+
382+
describe('enableEslintLoader() adds the eslint-loader', () => {
383+
it('without enableEslintLoader()', () => {
384+
const config = createConfig();
385+
config.addEntry('main', './main');
386+
config.publicPath = '/';
387+
config.outputPath = '/tmp';
388+
389+
const actualConfig = configGenerator(config);
390+
391+
expect(JSON.stringify(actualConfig.module.rules)).to.not.contain('eslint-loader');
392+
});
393+
394+
it('enableEslintLoader()', () => {
395+
const config = createConfig();
396+
config.addEntry('main', './main');
397+
config.publicPath = '/';
398+
config.outputPath = '/tmp';
399+
config.enableEslintLoader();
400+
401+
const actualConfig = configGenerator(config);
402+
403+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
404+
});
405+
406+
it('enableEslintLoader("extends-name")', () => {
407+
const config = createConfig();
408+
config.addEntry('main', './main');
409+
config.publicPath = '/';
410+
config.outputPath = '/tmp';
411+
config.enableEslintLoader('extends-name');
412+
413+
const actualConfig = configGenerator(config);
414+
415+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
416+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
417+
});
418+
419+
it('enableEslintLoader({extends: "extends-name"})', () => {
420+
const config = createConfig();
421+
config.addEntry('main', './main');
422+
config.publicPath = '/';
423+
config.outputPath = '/tmp';
424+
config.enableEslintLoader({ extends: 'extends-name' });
425+
426+
const actualConfig = configGenerator(config);
380427

428+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
429+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
430+
});
431+
432+
it('enableEslintLoader((options) => ...)', () => {
433+
const config = createConfig();
434+
config.addEntry('main', './main');
435+
config.publicPath = '/';
436+
config.outputPath = '/tmp';
437+
config.enableEslintLoader((options) => {
438+
options.extends = 'extends-name';
439+
});
440+
441+
const actualConfig = configGenerator(config);
442+
443+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
444+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
445+
});
381446
});
382447

383448
describe('addLoader() adds a custom loader', () => {

test/loaders/eslint.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 expect = require('chai').expect;
13+
const WebpackConfig = require('../../lib/WebpackConfig');
14+
const RuntimeConfig = require('../../lib/config/RuntimeConfig');
15+
const eslintLoader = require('../../lib/loaders/eslint');
16+
17+
function createConfig() {
18+
const runtimeConfig = new RuntimeConfig();
19+
runtimeConfig.context = __dirname;
20+
runtimeConfig.babelRcFileExists = false;
21+
22+
return new WebpackConfig(runtimeConfig);
23+
}
24+
25+
describe('loaders/eslint', () => {
26+
it('getOptions() full usage', () => {
27+
const config = createConfig();
28+
config.enableEslintLoader();
29+
const actualOptions = eslintLoader.getOptions(config);
30+
31+
expect(Object.keys(actualOptions)).to.have.lengthOf(4);
32+
});
33+
34+
it('getOptions() with extra options', () => {
35+
const config = createConfig();
36+
config.enableEslintLoader((options) => {
37+
options.extends = 'airbnb';
38+
});
39+
40+
const actualOptions = eslintLoader.getOptions(config);
41+
42+
expect(Object.keys(actualOptions)).to.have.lengthOf(5);
43+
expect(actualOptions.extends).to.equal('airbnb');
44+
});
45+
46+
it('getOptions() with an overridden option', () => {
47+
const config = createConfig();
48+
config.enableEslintLoader((options) => {
49+
options.emitWarning = false;
50+
});
51+
52+
const actualOptions = eslintLoader.getOptions(config);
53+
54+
expect(Object.keys(actualOptions)).to.have.lengthOf(4);
55+
expect(actualOptions.emitWarning).to.equal(false);
56+
});
57+
});

0 commit comments

Comments
 (0)