diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md index c74815a4db9..ef720449356 100644 --- a/packages/react-dev-utils/README.md +++ b/packages/react-dev-utils/README.md @@ -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` diff --git a/packages/react-dev-utils/eslintFormatter.js b/packages/react-dev-utils/eslintFormatter.js new file mode 100644 index 00000000000..dd971077639 --- /dev/null +++ b/packages/react-dev-utils/eslintFormatter.js @@ -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; diff --git a/packages/react-dev-utils/formatWebpackMessages.js b/packages/react-dev-utils/formatWebpackMessages.js index ad12e0ac931..f1262aad56b 100644 --- a/packages/react-dev-utils/formatWebpackMessages.js +++ b/packages/react-dev-utils/formatWebpackMessages.js @@ -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) { @@ -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. diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 918b18ed1ed..64a368c1bba 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -15,6 +15,7 @@ "checkRequiredFiles.js", "clearConsole.js", "crashOverlay.js", + "eslintFormatter.js", "FileSizeReporter.js", "formatWebpackMessages.js", "getProcessForPort.js", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 3d18991c584..a7cce11aae3 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -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'); @@ -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', }, ], diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index f51617005b9..fc8869de863 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -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'); @@ -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', }, ], diff --git a/packages/react-scripts/scripts/utils/createWebpackCompiler.js b/packages/react-scripts/scripts/utils/createWebpackCompiler.js index a333165b082..260b36c9756 100644 --- a/packages/react-scripts/scripts/utils/createWebpackCompiler.js +++ b/packages/react-scripts/scripts/utils/createWebpackCompiler.js @@ -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.' ); } });