Skip to content
This repository has been archived by the owner on Jul 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #1 from rogeliog/initial-approach
Browse files Browse the repository at this point in the history
[Proposal] Initial approach
  • Loading branch information
geowarin authored Aug 21, 2016
2 parents e14d70c + 223fbcf commit f93ccc7
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 67 deletions.
43 changes: 5 additions & 38 deletions src/formatMessage.js → src/extractError.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,17 @@
const RequestShortener = require("webpack/lib/RequestShortener");
const requestShortener = new RequestShortener(process.cwd());

function cleanStackTrace (message) {
return message
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, ''); // at ... ...:x:y
}

function isBabelSyntaxError (e) {
return e.name === 'ModuleBuildError' && e.message.indexOf('SyntaxError') >= 0;
}

function isModuleNotFoundError (e) {
return e.name === 'ModuleNotFoundError'
&& e.message.indexOf('Module not found') === 0
&& e.dependencies && e.dependencies.length;
}

function formatMessage (webpackError) {

const error = extractError(webpackError);
if (isBabelSyntaxError(webpackError)) {
error.message = cleanStackTrace(error.message + '\n');
error.type = 'babel-syntax-error';
error.severity = 1000;
} else if (isModuleNotFoundError(webpackError)) {
error.message = `Module not found ${webpackError.dependencies[0].request}`;
error.module = webpackError.dependencies[0].request;
error.type = 'module-not-found';
error.severity = 900;
} else {
error.severity = 0;
}

return error;
}

function extractError (e) {
return {
message: e.message,
file: getFile(e),
origin: getOrigin(e),
name: e.name
name: e.name,
severity: 0,
webpackError: e,
};
}


function getFile (e) {
if (e.file) {
return e.file;
Expand All @@ -66,12 +33,12 @@ function getOrigin (e) {
(dep.loc.start.line !== dep.loc.end.line ? dep.loc.end.line + ':' : '') + dep.loc.end.column;
});
var current = e.origin;
while (current.issuer) {
while (current.issuer && typeof current.issuer.readableIdentifier === 'function') {
current = current.issuer;
origin += '\n @ ' + current.readableIdentifier(requestShortener);
}
}
return origin;
}

module.exports = formatMessage;
module.exports = extractError;
8 changes: 8 additions & 0 deletions src/formatErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function formatErrors(errors, formatters, errorType) {
const format = (formatter) => formatter(errors, errorType) || [];
const flatten = (accum, curr) => accum.concat(curr);

return formatters.map(format).reduce(flatten, [])
}

module.exports = formatErrors;
27 changes: 27 additions & 0 deletions src/formatters/defaultError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const chalk = require('chalk');

function displayError(index, severity, { file, message, origin }) {
const baseError = chalk.red(`${index + 1}) ${severity}`);

return [
`${baseError} ${file ? 'in ' + file : ''}`,
'',
message,
(origin ? origin : undefined),
''
].filter((chunk) => chunk !== undefined);
}

function isDefaultError(error) {
return !error.type || error.type === 'babel-syntax-error';
}

function format(errors, type) {
return errors
.filter(isDefaultError)
.reduce((accum, error, i ) => (
accum.concat(displayError(i, type, error))
), []);
}

module.exports = format;
37 changes: 37 additions & 0 deletions src/formatters/moduleNotFound.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
function dependenciesNotFound(count) {
if (count === 1) {
return 'This dependency was not found in node_modules:';
}

return 'These dependencies were not found in node_modules:';
}

function forgetToInstall(count) {
if (count === 1) {
return 'Did you forget to run npm install --save for it?';
}

return 'Did you forget to run npm install --save for them?';
}

function formatErrors(errors) {
if (errors.length === 0) {
return [];
}

return [
dependenciesNotFound(errors.length),
'',
...errors.map(({ module }) =>`* ${module}`),
'',
forgetToInstall(errors.length),
];
}

function format(errors) {
return formatErrors(errors.filter((e) => (
e.type === 'module-not-found'
)));
}

module.exports = format;
53 changes: 27 additions & 26 deletions src/friendly-errors-plugin.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
const path = require('path');
const chalk = require('chalk');
const os = require('os');
const formatMessage = require('./formatMessage');
const processErrors = require('./processError');
const formatErrors = require('./formatErrors');
const debug = require('./debug');

const transformers = [
require('./transformers/babelSyntax'),
require('./transformers/moduleNotFound'),
];

const formatters = [
require('./formatters/moduleNotFound'),
require('./formatters/defaultError'),
];

function safeRequire (moduleName) {
try {
return require(moduleName);
Expand Down Expand Up @@ -36,44 +47,34 @@ class FriendlyErrorsWebpackPlugin {

const hasErrors = stats.hasErrors();
const hasWarnings = stats.hasWarnings();

if (!hasErrors && !hasWarnings) {
const time = stats.endTime - stats.startTime;
debug.log(chalk.green('Compiled successfully in ' + time + 'ms'));
if (this.compilationSuccessMessage) {
debug.log(this.compilationSuccessMessage);
}
return;
}

if (hasErrors) {
let formattedErrors = stats.compilation.errors.map(formatMessage);
const nbErrors = formattedErrors.length;
} else if (hasErrors) {
const { errors } = stats.compilation;
const processedErrors = processErrors(errors, transformers);
const nbErrors = processedErrors.length;
displayCompilationMessage(`Failed to compile with ${nbErrors} errors`, 'red');

if (this.notifier) {
this.notify('Error', formattedErrors[0]);
this.notify('Error', processedErrors[0]);
}

formattedErrors = getMaxSeverityErrors(formattedErrors, 'severity');
if (formattedErrors[0].type === 'module-not-found') {
debug.log('These dependencies were not found in node_modules:');
debug.log();
formattedErrors.forEach((error, index) => debug.log('*', error.module));
debug.log();
debug.log('Did you forget to run npm install --save for them?')
} else {
formattedErrors.forEach((error, index) => displayError(index, 'Error', error));
}

return;
}

if (hasWarnings) {
const formattedWarnings = stats.compilation.warnings.map(formatMessage);
const nbWarning = formattedWarnings.length;
const topErrors = getMaxSeverityErrors(processedErrors, 'severity');
formatErrors(topErrors, formatters, 'Error')
.forEach((chunk) => debug.log(chunk));
} else if (hasWarnings) {
const { warnings } = stats.compilation;
const processedWarns = processErrors(warnings, transformers);
const nbWarning = processedWarns.length;
displayCompilationMessage(`Compiled with ${nbWarning} warnings`, 'yellow');

formattedWarnings.forEach((warning, index) => displayError(index, 'Warning', warning));
formatErrors(processedWarns, formatters, 'Warning')
.forEach((chunk) => debug.log(chunk));
}
});

Expand Down
10 changes: 10 additions & 0 deletions src/processError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const extractError = require('./extractError');

function processErrors(errors, transformers) {
const transform = (error, transformer) => transformer(error);
const applyTransformations = (error) => transformers.reduce(transform, error);

return errors.map(extractError).map(applyTransformations);
}

module.exports = processErrors;
26 changes: 26 additions & 0 deletions src/transformers/babelSyntax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const TYPE = 'babel-syntax-error';

function cleanStackTrace(message) {
return message
.replace(/^\s*at\s.*:\d+:\d+[\s\)]*\n/gm, ''); // at ... ...:x:y
}

function isBabelSyntaxError(e) {
return e.type === TYPE ||
e.name === 'ModuleBuildError' &&
e.message.indexOf('SyntaxError') >= 0;
}

function transform(error) {
if (isBabelSyntaxError(error)) {
return Object.assign({}, error, {
message: cleanStackTrace(error.message + '\n'),
type: TYPE,
severity: 1000,
});
}

return error;
}

module.exports = transform;
26 changes: 26 additions & 0 deletions src/transformers/moduleNotFound.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const TYPE = 'module-not-found';

function isModuleNotFoundError(e) {
const { webpackError = {} } = e;
return webpackError.dependencies && webpackError.dependencies.length &&
(e.type === TYPE ||
e.name === 'ModuleNotFoundError' &&
e.message.indexOf('Module not found') === 0);
}

function transform(error) {
const webpackError = error.webpackError;
if (isModuleNotFoundError(error)) {
const module = webpackError.dependencies[0].request;
return Object.assign({}, error, {
message: `Module not found ${module}`,
type: TYPE,
severity: 900,
module,
});
}

return error;
}

module.exports = transform;
6 changes: 3 additions & 3 deletions test/integration.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ test('integration : babel syntax error', t => {
'',
'1) Error in ./fixtures/babel-syntax/index.js',
'',
`Module build failed: SyntaxError: /Users/geowarin/dev/projects/friendly-errors-webpack-plugin/test/fixtures/babel-syntax/index.js: Unexpected token (5:11)
3 |
`Module build failed: SyntaxError: ${__dirname}/fixtures/babel-syntax/index.js: Unexpected token (5:11)
3 |${' '}
4 | render() {
> 5 | return <div>
| ^
Expand All @@ -43,4 +43,4 @@ test('integration : babel syntax error', t => {
''
]);
debug.endCapture();
});
});
28 changes: 28 additions & 0 deletions test/unit/formatErrors.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const formatErrors = require('../../src/formatErrors');
const expect = require('expect');
const test = require('ava');

const simple = (errors) => errors
.filter(({ type }) => !type).map(({ message }) => message);

const allCaps = (errors) => errors
.filter(({ type }) => type == 'other').map((e) => e.message.toUpperCase());

const notFound = (errors) => errors
.filter(({ type }) => type === 'not-found').map(() => 'Not found');

const formatters = [allCaps]

test('formats the error based on the matching formatters', () => {
const errors = [
{ message: 'Error 1', type: undefined },
{ message: 'Error 2', type: 'other' },
{ message: 'Error 3', type: 'not-found' },
];

expect(formatErrors(errors, [simple, allCaps, notFound], 'Error')).toEqual([
'Error 1',
'ERROR 2',
'Not found',
]);
});
31 changes: 31 additions & 0 deletions test/unit/formatters/defaultError.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const defaultError = require('../../../src/formatters/defaultError');
const expect = require('expect');
const test = require('ava');
const chalk = require('chalk');

const noColor = (arr) => arr.map(chalk.stripColor)
const error = { message: 'Error message', file: './src/index.js' };

test('Formats errors with no type', () => {
expect(noColor(defaultError([error], 'Warning'))).toEqual([
'1) Warning in ./src/index.js',
'',
'Error message',
'',
]);
});

test('Formats babel-syntax-errors', () => {
const babeError = { ...error, type: 'babel-syntax-error' };
expect(noColor(defaultError([babeError], 'Error'))).toEqual([
'1) Error in ./src/index.js',
'',
'Error message',
'',
]);
});

test('Does not format other errors', () => {
const otherError = { ...error, type: 'other-error' };
expect(noColor(defaultError([otherError], 'Error'))).toEqual([]);
});
Loading

0 comments on commit f93ccc7

Please sign in to comment.