UNPKG

4.24 kBJavaScriptView Raw
1const Future = require('fluture');
2const merge = require('deepmerge');
3const clone = require('lodash.clonedeep');
4const { CLIEngine } = require('eslint');
5const {
6 assoc, curry, evolve, keys, omit, pipe, prop, reduce
7} = require('ramda');
8const { basename, join } = require('path');
9
10const MODULES = join(__dirname, 'node_modules');
11const getEslintOptions = config => config.module.rule('lint').use('eslint').get('options');
12const renameKeys = curry((definition, obj) =>
13 reduce((acc, key) => assoc(definition[key] || key, obj[key], acc), {}, keys(obj)));
14const reduceToTrueKeys = reduce((acc, key) => assoc(key, true, acc), {});
15const getEslintRcConfig = pipe(
16 getEslintOptions,
17 clone,
18 // We remove these keys since they are needed when running the lint command but
19 // not the eslintrc command. The lint command uses ESLint's CLIEngine, but the
20 // ESLint RC format does not match the CLIEngine format exactly. We must remove
21 // anything we add that does not comply with ESLint's schemas.
22 // https://github.com/eslint/eslint/blob/9d1df92628dd4dd1e70fbb19454008e146387435/conf/config-schema.js
23 // https://github.com/eslint/eslint/blob/9d1df92628dd4dd1e70fbb19454008e146387435/lib/config/config-validator.js#L167
24 omit(['failOnError', 'emitWarning', 'emitError', 'cwd', 'useEslintrc', 'fix', 'extensions']),
25 renameKeys({ envs: 'env', baseConfig: 'extends' }),
26 evolve({
27 extends: prop('extends'),
28 globals: reduceToTrueKeys,
29 env: reduceToTrueKeys
30 })
31);
32
33module.exports = (neutrino, opts = {}) => {
34 const failBuild = process.env.NODE_ENV !== 'development' || neutrino.options.command !== 'start';
35 const options = merge.all([
36 opts,
37 !opts.include ? { include: [neutrino.options.source] } : {}
38 ]);
39
40 neutrino.config
41 .resolve
42 .modules
43 .add(MODULES)
44 .end()
45 .end()
46 .resolveLoader
47 .modules
48 .add(MODULES)
49 .end()
50 .end()
51 .module
52 .rule('lint')
53 .test(options.test || /\.(js|jsx)$/)
54 .pre()
55 .when(options.include, rule => rule.include.merge(options.include))
56 .when(options.exclude, rule => rule.exclude.merge(options.exclude))
57 .use('eslint')
58 .loader(require.resolve('eslint-loader'))
59 .options(merge({
60 failOnError: failBuild,
61 emitWarning: failBuild,
62 emitError: failBuild,
63 cwd: neutrino.options.root,
64 useEslintrc: false,
65 root: true,
66 // eslint-loader uses executeOnText(), which ignores the `extensions` setting.
67 // However it's still needed for the lint command, as it uses executeOnFiles().
68 extensions: ['js', 'jsx'],
69 plugins: ['babel'],
70 baseConfig: {},
71 envs: ['es6'],
72 parser: 'babel-eslint',
73 parserOptions: {
74 ecmaVersion: 2017,
75 sourceType: 'module',
76 ecmaFeatures: {
77 objectLiteralDuplicateProperties: false,
78 generators: true,
79 impliedStrict: true
80 }
81 },
82 settings: {},
83 globals: ['process'],
84 rules: {}
85 }, options.eslint || {}));
86
87 neutrino.register('eslintrc', () => getEslintRcConfig(neutrino.config));
88 neutrino.register('lint', () => {
89 const { fix = false } = neutrino.options.args;
90 const ignorePattern = (options.exclude || [])
91 .map(exclude => join(
92 basename(neutrino.options.source),
93 basename(exclude),
94 '**/*'
95 ));
96 const eslintConfig = merge(getEslintOptions(neutrino.config), { ignorePattern, fix });
97
98 return Future
99 .of(eslintConfig)
100 .map(options => new CLIEngine(options))
101 .chain(cli => Future.both(Future.of(cli.executeOnFiles(options.include)), Future.of(cli.getFormatter())))
102 .map(([report, formatter]) => {
103 fix && CLIEngine.outputFixes(report);
104 return [report, formatter];
105 })
106 .chain(([report, formatter]) => {
107 const errors = CLIEngine.getErrorResults(report.results);
108 const formatted = formatter(report.results);
109
110 return errors.length ? Future.reject(formatted) : Future.of(formatted);
111 });
112 });
113};