Skip to content

Commit

Permalink
Add custom eslint formatter (#2138)
Browse files Browse the repository at this point in the history
* Add custom eslint formatter

* Add formatter docs

* Update formatter docs

* Slightly tweak it

* Update README.md
  • Loading branch information
sidoshi authored and gaearon committed May 14, 2017
1 parent 10c734b commit f17448e
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 71 deletions.
30 changes: 30 additions & 0 deletions packages/react-dev-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,36 @@ clearConsole();
console.log('Just cleared the screen!');
```

#### `eslintFormatter(results: Object): string`

This is our custom ESLint formatter that integrates well with Create React App console output.
You can use the default one instead if you prefer so.

```js
const eslintFormatter = require('react-dev-utils/eslintFormatter');

// In your webpack config:
// ...
module: {
rules: [
{
test: /\.(js|jsx)$/,
include: paths.appSrc,
enforce: 'pre',
use: [
{
loader: 'eslint-loader',
options: {
// Pass the formatter:
formatter: eslintFormatter,
},
},
],
}
]
}
```

#### `FileSizeReporter`

##### `measureFileSizesBeforeBuild(buildFolder: string): Promise<OpaqueFileSizes>`
Expand Down
71 changes: 71 additions & 0 deletions packages/react-dev-utils/eslintFormatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

const chalk = require('chalk');
const table = require('text-table');

function isError(message) {
if (message.fatal || message.severity === 2) {
return true;
}
return false;
}

function formatter(results) {
let output = '\n';

let hasErrors = false;
let hasWarnings = false;

results.forEach(result => {
let messages = result.messages;
if (messages.length === 0) {
return;
}

let hasErrors = false;
messages = messages.map(message => {
let messageType;
if (isError(message)) {
messageType = 'error';
hasErrors = true;
} else {
messageType = 'warn';
hasWarnings = true;
}

let line = message.line || 0;
let column = message.column || 0;
let position = chalk.dim(`${line}:${column}`);
return [
'',
position,
messageType,
message.message.replace(/\.$/, ''),
chalk.dim(message.ruleId || ''),
];
});

// if there are error messages, we want to show only errors
if (hasErrors) {
messages = messages.filter(m => m[2] === 'error');
}

// add color to messageTypes
messages.forEach(m => {
m[2] = m[2] === 'error' ? chalk.red(m[2]) : chalk.yellow(m[2]);
});

let outputTable = table(messages, {
align: ['l', 'l', 'l'],
stringLength(str) {
return chalk.stripColor(str).length;
},
});

output += `${outputTable}\n\n`;
});

return output;
}

module.exports = formatter;
60 changes: 2 additions & 58 deletions packages/react-dev-utils/formatWebpackMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// This is quite hacky and hopefully won't be needed when Webpack fixes this.
// https://github.com/webpack/webpack/issues/2878

var chalk = require('chalk');
var friendlySyntaxErrorLabel = 'Syntax error:';

function isLikelyASyntaxError(message) {
Expand Down Expand Up @@ -85,65 +86,8 @@ function formatMessage(message, isError) {
);
}

// TODO: Ideally we should write a custom ESLint formatter instead.

// If the second line already includes a filename, and it's a warning,
// this is likely coming from ESLint. Skip it because Webpack also prints it.
// Let's omit that in this case.
var BEGIN_ESLINT_FILENAME = String.fromCharCode(27) + '[4m';
// Also filter out ESLint summaries for each file
var BEGIN_ESLINT_WARNING_SUMMARY = String.fromCharCode(27) +
'[33m' +
String.fromCharCode(27) +
'[1m' +
String.fromCharCode(10006);
var BEGIN_ESLINT_ERROR_SUMMARY = String.fromCharCode(27) +
'[31m' +
String.fromCharCode(27) +
'[1m' +
String.fromCharCode(10006);
// ESLint puts separators like this between groups. We don't need them:
var ESLINT_EMPTY_SEPARATOR = String.fromCharCode(27) +
'[22m' +
String.fromCharCode(27) +
'[39m';
// Go!
lines = lines.filter(function(line) {
if (line === ESLINT_EMPTY_SEPARATOR) {
return false;
}
if (
line.indexOf(BEGIN_ESLINT_FILENAME) === 0 ||
line.indexOf(BEGIN_ESLINT_WARNING_SUMMARY) === 0 ||
line.indexOf(BEGIN_ESLINT_ERROR_SUMMARY) === 0
) {
return false;
}
return true;
});

var ESLINT_WARNING_LABEL = String.fromCharCode(27) +
'[33m' +
'warning' +
String.fromCharCode(27) +
'[39m';
// If there were errors, omit any warnings.
if (isError) {
lines = lines.filter(function(line) {
return line.indexOf(ESLINT_WARNING_LABEL) === -1;
});
}

// Prepend filename with an explanation.
lines[0] =
// Underline
String.fromCharCode(27) +
'[4m' +
// Filename
lines[0] +
// End underline
String.fromCharCode(27) +
'[24m' +
lines[0] = chalk.underline(lines[0]) +
(isError ? ' contains errors.' : ' contains warnings.');

// Reassemble the message.
Expand Down
1 change: 1 addition & 0 deletions packages/react-dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"checkRequiredFiles.js",
"clearConsole.js",
"crashOverlay.js",
"eslintFormatter.js",
"FileSizeReporter.js",
"formatWebpackMessages.js",
"getProcessForPort.js",
Expand Down
7 changes: 4 additions & 3 deletions packages/react-scripts/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const getClientEnvironment = require('./env');
const paths = require('./paths');

Expand Down Expand Up @@ -120,16 +121,16 @@ module.exports = {
enforce: 'pre',
use: [
{
// @remove-on-eject-begin
// Point ESLint to our predefined config.
options: {
formatter: eslintFormatter,
// @remove-on-eject-begin
baseConfig: {
extends: ['react-app'],
},
ignore: false,
useEslintrc: false,
// @remove-on-eject-end
},
// @remove-on-eject-end
loader: 'eslint-loader',
},
],
Expand Down
8 changes: 5 additions & 3 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const paths = require('./paths');
const getClientEnvironment = require('./env');

Expand Down Expand Up @@ -117,17 +118,18 @@ module.exports = {
enforce: 'pre',
use: [
{
// @remove-on-eject-begin
// Point ESLint to our predefined config.
options: {
formatter: eslintFormatter,
// @remove-on-eject-begin
// TODO: consider separate config for production,
// e.g. to enable no-console and no-debugger only in production.
baseConfig: {
extends: ['react-app'],
},
ignore: false,
useEslintrc: false,
// @remove-on-eject-end
},
// @remove-on-eject-end
loader: 'eslint-loader',
},
],
Expand Down
14 changes: 7 additions & 7 deletions packages/react-scripts/scripts/utils/createWebpackCompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,17 @@ module.exports = function createWebpackCompiler(config, onReadyCallback) {
console.log(message);
console.log();
});

// Teach some ESLint tricks.
console.log('You may use special comments to disable some warnings.');
console.log(
'Use ' +
chalk.yellow('// eslint-disable-next-line') +
' to ignore the next line.'
'Search the ' +
chalk.dim('keywords') +
' from the right column to learn more.'
);
console.log(
'Use ' +
chalk.yellow('/* eslint-disable */') +
' to ignore all warnings in a file.'
'To ignore, add ' +
chalk.yellow('// eslint-disable-next-line') +
' to the line before.'
);
}
});
Expand Down

0 comments on commit f17448e

Please sign in to comment.