Skip to content

Commit fab12a4

Browse files
author
Jeroen Meeus
committed
symfony#232 Added support for the eslint-loader
1 parent 00e4051 commit fab12a4

File tree

9 files changed

+635
-181
lines changed

9 files changed

+635
-181
lines changed

index.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,11 @@ const publicApi = {
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 {string} plugin
296298
* @param {number} priority
@@ -629,6 +631,40 @@ const publicApi = {
629631
return this;
630632
},
631633

634+
/**
635+
* If enabled, the eslint-loader is enabled.
636+
*
637+
* https://github.com/MoOx/eslint-loader
638+
*
639+
* // enables the eslint loaded using the default eslint configuration.
640+
* Encore.enableEslint();
641+
*
642+
* // Optionally, you can pass in the configuration eslint should extend.
643+
* Encore.enableEslint('airbnb');
644+
*
645+
* // You can also pass in an object of options
646+
* // that will be passed on to the eslint-loader
647+
* Encore.enableEslint({
648+
* extends: 'airbnb',
649+
emitWarning: false
650+
* });
651+
*
652+
* // For a mroe advanced usage you can pass in a callback
653+
* // https://github.com/MoOx/eslint-loader#options
654+
* Encore.enableEslint((options) => {
655+
* options.extends = 'airbnb';
656+
* options.emitWarning = fasle;
657+
* });
658+
*
659+
* @param {string|object|function} eslintLoaderOptionsOrCallback
660+
* @returns {exports}
661+
*/
662+
enableEslintLoader(eslintLoaderOptionsOrCallback = () => {}) {
663+
webpackConfig.enableEslintLoader(eslintLoaderOptionsOrCallback);
664+
665+
return this;
666+
},
667+
632668
/**
633669
* If enabled, display build notifications using
634670
* webpack-notifier.

lib/WebpackConfig.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class WebpackConfig {
5959
this.useReact = false;
6060
this.usePreact = false;
6161
this.useVueLoader = false;
62+
this.useEslintLoader = false;
6263
this.useTypeScriptLoader = false;
6364
this.useForkedTypeScriptTypeChecking = false;
6465
this.useWebpackNotifier = false;
@@ -78,6 +79,7 @@ class WebpackConfig {
7879
this.stylusLoaderOptionsCallback = () => {};
7980
this.babelConfigurationCallback = () => {};
8081
this.vueLoaderOptionsCallback = () => {};
82+
this.eslintLoaderOptionsCallback = () => {};
8183
this.tsConfigurationCallback = () => {};
8284

8385
// Plugins options
@@ -403,6 +405,31 @@ class WebpackConfig {
403405
this.vueLoaderOptionsCallback = vueLoaderOptionsCallback;
404406
}
405407

408+
enableEslintLoader(eslintLoaderOptionsOrCallback = () => {}) {
409+
this.useEslintLoader = true;
410+
411+
if (typeof eslintLoaderOptionsOrCallback === 'function') {
412+
this.eslintLoaderOptionsCallback = eslintLoaderOptionsOrCallback;
413+
return;
414+
}
415+
416+
if (typeof eslintLoaderOptionsOrCallback === 'string') {
417+
this.eslintLoaderOptionsCallback = (options) => {
418+
options.extends = eslintLoaderOptionsOrCallback;
419+
};
420+
return;
421+
}
422+
423+
if (typeof eslintLoaderOptionsOrCallback === 'object') {
424+
this.eslintLoaderOptionsCallback = (options) => {
425+
Object.assign(options, eslintLoaderOptionsOrCallback);
426+
};
427+
return;
428+
}
429+
430+
throw new Error('Argument 1 to enableEslintLoader() must be either a string, object or callback function.');
431+
}
432+
406433
enableBuildNotifications(enabled = true, notifierPluginOptionsCallback = () => {}) {
407434
if (typeof notifierPluginOptionsCallback !== 'function') {
408435
throw new Error('Argument 2 to enableBuildNotifications() must be a callback function.');

lib/config-generator.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const stylusLoaderUtil = require('./loaders/stylus');
1919
const babelLoaderUtil = require('./loaders/babel');
2020
const tsLoaderUtil = require('./loaders/typescript');
2121
const vueLoaderUtil = require('./loaders/vue');
22+
const eslintLoaderUtil = require('./loaders/eslint');
2223
// plugins utils
2324
const extractTextPluginUtil = require('./plugins/extract-text');
2425
const deleteUnusedEntriesPluginUtil = require('./plugins/delete-unused-entries');
@@ -201,6 +202,16 @@ class ConfigGenerator {
201202
});
202203
}
203204

205+
if (this.webpackConfig.useEslintLoader) {
206+
rules.push({
207+
test: /\.jsx?$/,
208+
loader: 'eslint-loader',
209+
exclude: /node_modules/,
210+
enforce: 'pre',
211+
options: eslintLoaderUtil.getOptions(this.webpackConfig)
212+
});
213+
}
214+
204215
if (this.webpackConfig.useTypeScriptLoader) {
205216
rules.push({
206217
test: /\.tsx?$/,

lib/features.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ const features = {
6363
packages: ['vue', 'vue-loader', 'vue-template-compiler'],
6464
description: 'load VUE files'
6565
},
66+
eslint: {
67+
method: 'enableEslintLoader()',
68+
// eslint is needed so the end-user can do things
69+
packages: ['eslint', 'eslint-loader', 'babel-eslint', 'eslint-plugin-import'],
70+
description: 'load VUE files'
71+
},
6672
notifier: {
6773
method: 'enableBuildNotifications()',
6874
packages: ['webpack-notifier'],

lib/loaders/eslint.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+
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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,16 @@
5050
},
5151
"devDependencies": {
5252
"autoprefixer": "^6.7.7",
53+
"babel-eslint": "^8.2.1",
5354
"babel-plugin-transform-react-jsx": "^6.24.1",
5455
"babel-preset-es2015": "^6.24.1",
5556
"babel-preset-react": "^6.23.0",
5657
"chai": "^3.5.0",
5758
"chai-fs": "^1.0.0",
58-
"eslint": "^3.19.0",
59+
"eslint": "^4.15.0",
60+
"eslint-loader": "^1.9.0",
5961
"eslint-plugin-header": "^1.0.0",
62+
"eslint-plugin-import": "^2.8.0",
6063
"eslint-plugin-node": "^4.2.2",
6164
"fork-ts-checker-webpack-plugin": "^0.2.7",
6265
"http-server": "^0.9.0",

test/config-generator.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,72 @@ describe('The config-generator function', () => {
333333
});
334334
});
335335

336+
describe('enableEslintLoader() adds the eslint-loader', () => {
337+
it('without enableEslintLoader()', () => {
338+
const config = createConfig();
339+
config.addEntry('main', './main');
340+
config.publicPath = '/';
341+
config.outputPath = '/tmp';
342+
343+
const actualConfig = configGenerator(config);
344+
345+
expect(JSON.stringify(actualConfig.module.rules)).to.not.contain('eslint-loader');
346+
});
347+
348+
it('enableEslintLoader()', () => {
349+
const config = createConfig();
350+
config.addEntry('main', './main');
351+
config.publicPath = '/';
352+
config.outputPath = '/tmp';
353+
config.enableEslintLoader();
354+
355+
const actualConfig = configGenerator(config);
356+
357+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
358+
});
359+
360+
it('enableEslintLoader("extends-name")', () => {
361+
const config = createConfig();
362+
config.addEntry('main', './main');
363+
config.publicPath = '/';
364+
config.outputPath = '/tmp';
365+
config.enableEslintLoader('extends-name');
366+
367+
const actualConfig = configGenerator(config);
368+
369+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
370+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
371+
});
372+
373+
it('enableEslintLoader({extends: "extends-name"})', () => {
374+
const config = createConfig();
375+
config.addEntry('main', './main');
376+
config.publicPath = '/';
377+
config.outputPath = '/tmp';
378+
config.enableEslintLoader({ extends: 'extends-name' });
379+
380+
const actualConfig = configGenerator(config);
381+
382+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
383+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
384+
});
385+
386+
it('enableEslintLoader((options) => ...)', () => {
387+
const config = createConfig();
388+
config.addEntry('main', './main');
389+
config.publicPath = '/';
390+
config.outputPath = '/tmp';
391+
config.enableEslintLoader((options) => {
392+
options.extends = 'extends-name';
393+
});
394+
395+
const actualConfig = configGenerator(config);
396+
397+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('eslint-loader');
398+
expect(JSON.stringify(actualConfig.module.rules)).to.contain('extends-name');
399+
});
400+
});
401+
336402
describe('addLoader() adds a custom loader', () => {
337403
it('addLoader()', () => {
338404
const config = createConfig();

test/loaders/eslint.js

Lines changed: 57 additions & 0 deletions
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)