Skip to content

Commit f17448e

Browse files
sidoshigaearon
authored andcommitted
Add custom eslint formatter (#2138)
* Add custom eslint formatter * Add formatter docs * Update formatter docs * Slightly tweak it * Update README.md
1 parent 10c734b commit f17448e

File tree

7 files changed

+120
-71
lines changed

7 files changed

+120
-71
lines changed

packages/react-dev-utils/README.md

+30
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,36 @@ clearConsole();
110110
console.log('Just cleared the screen!');
111111
```
112112

113+
#### `eslintFormatter(results: Object): string`
114+
115+
This is our custom ESLint formatter that integrates well with Create React App console output.
116+
You can use the default one instead if you prefer so.
117+
118+
```js
119+
const eslintFormatter = require('react-dev-utils/eslintFormatter');
120+
121+
// In your webpack config:
122+
// ...
123+
module: {
124+
rules: [
125+
{
126+
test: /\.(js|jsx)$/,
127+
include: paths.appSrc,
128+
enforce: 'pre',
129+
use: [
130+
{
131+
loader: 'eslint-loader',
132+
options: {
133+
// Pass the formatter:
134+
formatter: eslintFormatter,
135+
},
136+
},
137+
],
138+
}
139+
]
140+
}
141+
```
142+
113143
#### `FileSizeReporter`
114144

115145
##### `measureFileSizesBeforeBuild(buildFolder: string): Promise<OpaqueFileSizes>`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use strict';
2+
3+
const chalk = require('chalk');
4+
const table = require('text-table');
5+
6+
function isError(message) {
7+
if (message.fatal || message.severity === 2) {
8+
return true;
9+
}
10+
return false;
11+
}
12+
13+
function formatter(results) {
14+
let output = '\n';
15+
16+
let hasErrors = false;
17+
let hasWarnings = false;
18+
19+
results.forEach(result => {
20+
let messages = result.messages;
21+
if (messages.length === 0) {
22+
return;
23+
}
24+
25+
let hasErrors = false;
26+
messages = messages.map(message => {
27+
let messageType;
28+
if (isError(message)) {
29+
messageType = 'error';
30+
hasErrors = true;
31+
} else {
32+
messageType = 'warn';
33+
hasWarnings = true;
34+
}
35+
36+
let line = message.line || 0;
37+
let column = message.column || 0;
38+
let position = chalk.dim(`${line}:${column}`);
39+
return [
40+
'',
41+
position,
42+
messageType,
43+
message.message.replace(/\.$/, ''),
44+
chalk.dim(message.ruleId || ''),
45+
];
46+
});
47+
48+
// if there are error messages, we want to show only errors
49+
if (hasErrors) {
50+
messages = messages.filter(m => m[2] === 'error');
51+
}
52+
53+
// add color to messageTypes
54+
messages.forEach(m => {
55+
m[2] = m[2] === 'error' ? chalk.red(m[2]) : chalk.yellow(m[2]);
56+
});
57+
58+
let outputTable = table(messages, {
59+
align: ['l', 'l', 'l'],
60+
stringLength(str) {
61+
return chalk.stripColor(str).length;
62+
},
63+
});
64+
65+
output += `${outputTable}\n\n`;
66+
});
67+
68+
return output;
69+
}
70+
71+
module.exports = formatter;

packages/react-dev-utils/formatWebpackMessages.js

+2-58
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// This is quite hacky and hopefully won't be needed when Webpack fixes this.
1717
// https://github.com/webpack/webpack/issues/2878
1818

19+
var chalk = require('chalk');
1920
var friendlySyntaxErrorLabel = 'Syntax error:';
2021

2122
function isLikelyASyntaxError(message) {
@@ -85,65 +86,8 @@ function formatMessage(message, isError) {
8586
);
8687
}
8788

88-
// TODO: Ideally we should write a custom ESLint formatter instead.
89-
90-
// If the second line already includes a filename, and it's a warning,
91-
// this is likely coming from ESLint. Skip it because Webpack also prints it.
92-
// Let's omit that in this case.
93-
var BEGIN_ESLINT_FILENAME = String.fromCharCode(27) + '[4m';
94-
// Also filter out ESLint summaries for each file
95-
var BEGIN_ESLINT_WARNING_SUMMARY = String.fromCharCode(27) +
96-
'[33m' +
97-
String.fromCharCode(27) +
98-
'[1m' +
99-
String.fromCharCode(10006);
100-
var BEGIN_ESLINT_ERROR_SUMMARY = String.fromCharCode(27) +
101-
'[31m' +
102-
String.fromCharCode(27) +
103-
'[1m' +
104-
String.fromCharCode(10006);
105-
// ESLint puts separators like this between groups. We don't need them:
106-
var ESLINT_EMPTY_SEPARATOR = String.fromCharCode(27) +
107-
'[22m' +
108-
String.fromCharCode(27) +
109-
'[39m';
110-
// Go!
111-
lines = lines.filter(function(line) {
112-
if (line === ESLINT_EMPTY_SEPARATOR) {
113-
return false;
114-
}
115-
if (
116-
line.indexOf(BEGIN_ESLINT_FILENAME) === 0 ||
117-
line.indexOf(BEGIN_ESLINT_WARNING_SUMMARY) === 0 ||
118-
line.indexOf(BEGIN_ESLINT_ERROR_SUMMARY) === 0
119-
) {
120-
return false;
121-
}
122-
return true;
123-
});
124-
125-
var ESLINT_WARNING_LABEL = String.fromCharCode(27) +
126-
'[33m' +
127-
'warning' +
128-
String.fromCharCode(27) +
129-
'[39m';
130-
// If there were errors, omit any warnings.
131-
if (isError) {
132-
lines = lines.filter(function(line) {
133-
return line.indexOf(ESLINT_WARNING_LABEL) === -1;
134-
});
135-
}
136-
13789
// Prepend filename with an explanation.
138-
lines[0] =
139-
// Underline
140-
String.fromCharCode(27) +
141-
'[4m' +
142-
// Filename
143-
lines[0] +
144-
// End underline
145-
String.fromCharCode(27) +
146-
'[24m' +
90+
lines[0] = chalk.underline(lines[0]) +
14791
(isError ? ' contains errors.' : ' contains warnings.');
14892

14993
// Reassemble the message.

packages/react-dev-utils/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"checkRequiredFiles.js",
1616
"clearConsole.js",
1717
"crashOverlay.js",
18+
"eslintFormatter.js",
1819
"FileSizeReporter.js",
1920
"formatWebpackMessages.js",
2021
"getProcessForPort.js",

packages/react-scripts/config/webpack.config.dev.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
1717
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
1818
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
1919
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
20+
const eslintFormatter = require('react-dev-utils/eslintFormatter');
2021
const getClientEnvironment = require('./env');
2122
const paths = require('./paths');
2223

@@ -120,16 +121,16 @@ module.exports = {
120121
enforce: 'pre',
121122
use: [
122123
{
123-
// @remove-on-eject-begin
124-
// Point ESLint to our predefined config.
125124
options: {
125+
formatter: eslintFormatter,
126+
// @remove-on-eject-begin
126127
baseConfig: {
127128
extends: ['react-app'],
128129
},
129130
ignore: false,
130131
useEslintrc: false,
132+
// @remove-on-eject-end
131133
},
132-
// @remove-on-eject-end
133134
loader: 'eslint-loader',
134135
},
135136
],

packages/react-scripts/config/webpack.config.prod.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
1717
const ExtractTextPlugin = require('extract-text-webpack-plugin');
1818
const ManifestPlugin = require('webpack-manifest-plugin');
1919
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
20+
const eslintFormatter = require('react-dev-utils/eslintFormatter');
2021
const paths = require('./paths');
2122
const getClientEnvironment = require('./env');
2223

@@ -117,17 +118,18 @@ module.exports = {
117118
enforce: 'pre',
118119
use: [
119120
{
120-
// @remove-on-eject-begin
121-
// Point ESLint to our predefined config.
122121
options: {
122+
formatter: eslintFormatter,
123+
// @remove-on-eject-begin
123124
// TODO: consider separate config for production,
124125
// e.g. to enable no-console and no-debugger only in production.
125126
baseConfig: {
126127
extends: ['react-app'],
127128
},
129+
ignore: false,
128130
useEslintrc: false,
131+
// @remove-on-eject-end
129132
},
130-
// @remove-on-eject-end
131133
loader: 'eslint-loader',
132134
},
133135
],

packages/react-scripts/scripts/utils/createWebpackCompiler.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,17 @@ module.exports = function createWebpackCompiler(config, onReadyCallback) {
100100
console.log(message);
101101
console.log();
102102
});
103+
103104
// Teach some ESLint tricks.
104-
console.log('You may use special comments to disable some warnings.');
105105
console.log(
106-
'Use ' +
107-
chalk.yellow('// eslint-disable-next-line') +
108-
' to ignore the next line.'
106+
'Search the ' +
107+
chalk.dim('keywords') +
108+
' from the right column to learn more.'
109109
);
110110
console.log(
111-
'Use ' +
112-
chalk.yellow('/* eslint-disable */') +
113-
' to ignore all warnings in a file.'
111+
'To ignore, add ' +
112+
chalk.yellow('// eslint-disable-next-line') +
113+
' to the line before.'
114114
);
115115
}
116116
});

0 commit comments

Comments
 (0)