Skip to content

Commit

Permalink
feat: New error and warning messages
Browse files Browse the repository at this point in the history
  • Loading branch information
d4rkr00t committed Jun 13, 2017
1 parent 9c215da commit 7e33a6f
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 52 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"browser": false
},

"parserOptions": {
"sourceType": "module"
},

"plugins": [
"flowtype"
],
Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,13 @@
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"jest": {
"roots": [
"./src"
],
"collectCoverageFrom": [
"src/**/*.js"
]
}
}
29 changes: 15 additions & 14 deletions src/commands/dev-server/webpack-dev-server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* @flow */
import fs from "fs";

import historyApiFallback from "connect-history-api-fallback";
import resolveModule from "resolve";
Expand All @@ -7,11 +8,9 @@ import WebpackDevServer from "webpack-dev-server";
import webpackConfigBuilder from "./../../webpack/config-builder";
import detectPort from "./../../utils/detect-port";
import testUtils from "./../../utils/test-utils";
import { isSyntaxError, formatMessages } from "./../../utils/error-helpers";
import {
isLikelyASyntaxError,
formatMessage
} from "./../../utils/error-helpers";
import {
separator,
clearConsole,
eslintExtraWarningMsg,
devServerInvalidBuildMsg,
Expand Down Expand Up @@ -47,12 +46,11 @@ export function onDone(
}

const json = stats.toJson({}, true);
const formattedWarnings = json.warnings.map(
message => "Warning in " + formatMessage(message)
);
let formattedErrors = json.errors.map(
message => "Error in " + formatMessage(message)
);

fs.writeFileSync("./error.json", JSON.stringify(json));

const formattedWarnings = formatMessages(json.warnings);
let formattedErrors = formatMessages(json.errors);

if (hasErrors) {
if (
Expand All @@ -68,19 +66,22 @@ export function onDone(
// If there are any syntax errors, show just them.
// This prevents a confusing ESLint parsing error
// preceding a much more useful Babel syntax error.
if (formattedErrors.some(isLikelyASyntaxError)) {
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
if (formattedErrors.some(isSyntaxError)) {
formattedErrors = formattedErrors.filter(isSyntaxError);
}

// If errors exist, ignore warnings.
formattedErrors.forEach(message => console.log("\n", message)); // eslint-disable-line
console.log(); // eslint-disable-line
console.log(formattedErrors.join(`\n\n${separator()}\n\n`)); // eslint-disable-line
testUtils();
return;
}

if (hasWarnings) {
devServerCompiledWithWarningsMsg(filename, flags, params, buildDuration);
formattedWarnings.forEach(message => console.log("\n", message)); // eslint-disable-line
console.log(); // eslint-disable-line
console.log(formattedWarnings.join(`\n\n${separator()}\n\n`)); // eslint-disable-line
console.log(`\n${separator()}\n`); // eslint-disable-line
eslintExtraWarningMsg();
}

Expand Down
37 changes: 37 additions & 0 deletions src/utils/__test__/__snapshots__/error-helpers.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`#formatMessages Format ESLint Warnings 1`] = `
" 5:16 warning React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead react/no-deprecated
40:11 warning No duplicate props allowed react/jsx-no-duplicate-props
Where: ./thinking-in-react/src/components/filtered-product-table/filtered-product-table.js
16:11 warning No duplicate props allowed react/jsx-no-duplicate-props
Where: ./thinking-in-react/src/components/product-table/product-table.js"
`;

exports[`#formatMessages Format Syntax Error 1`] = `
"Error:
| Syntax Error: Unexpected token (20:7)
Where: ./thinking-in-react/src/components/product-table/product-table.js
  18 |  return (
 19 |  <div className=\\"productTable\\">
> 20 |  <<table>
 |  ^
 21 |  <thead>
 22 |  <tr>
 23 |  <th>Name</th>"
`;

exports[`#formatMessages Unknown messages 1`] = `
Array [
"some",
"custom",
"messages",
]
`;
21 changes: 19 additions & 2 deletions src/utils/__test__/error-helpers.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
test("adds 1 + 2 to equal 3", () => {
expect(1 + 2).toBe(3);
import syntaxErrorMock from "./mock-data/syntax-error.json";
import eslintWarningMock from "./mock-data/eslint-warning.json";
import { formatMessages } from "../error-helpers";

describe("#formatMessages", () => {
test("Format Syntax Error", () => {
const [error] = formatMessages(syntaxErrorMock);
expect(error).toMatchSnapshot();
});

test("Format ESLint Warnings", () => {
const warnings = formatMessages(eslintWarningMock).join("\n\n\n");
expect(warnings).toMatchSnapshot();
});

test("Unknown messages", () => {
const messages = ["some", "custom", "messages"];
expect(formatMessages(messages)).toMatchSnapshot();
});
});
4 changes: 4 additions & 0 deletions src/utils/__test__/mock-data/eslint-warning.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"./thinking-in-react/src/components/filtered-product-table/filtered-product-table.js\n\n\u001b[4m/Users/ssysoev/Development/aik-examples/thinking-in-react/src/components/filtered-product-table/filtered-product-table.js\u001b[24m\n \u001b[2m5:16\u001b[22m \u001b[33mwarning\u001b[39m React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead \u001b[2mreact/no-deprecated\u001b[22m\n \u001b[2m40:11\u001b[22m \u001b[33mwarning\u001b[39m No duplicate props allowed \u001b[2mreact/jsx-no-duplicate-props\u001b[22m\n\n\u001b[33m\u001b[1m✖ 2 problems (0 errors, 2 warnings)\n\u001b[22m\u001b[39m\n @ ./thinking-in-react/src/index.js 14:28-95\n @ ../aik/lib/webpack/assets/react-entry-point.js\n @ multi ../aik/~/webpack-dev-server/client?http://localhost:4444/ ../aik/~/webpack/hot/dev-server.js ../aik/lib/webpack/assets/react-entry-point.js",
"./thinking-in-react/src/components/product-table/product-table.js\n\n\u001b[4m/Users/ssysoev/Development/aik-examples/thinking-in-react/src/components/product-table/product-table.js\u001b[24m\n \u001b[2m16:11\u001b[22m \u001b[33mwarning\u001b[39m No duplicate props allowed \u001b[2mreact/jsx-no-duplicate-props\u001b[22m\n\n\u001b[33m\u001b[1m✖ 1 problem (0 errors, 1 warning)\n\u001b[22m\u001b[39m\n @ ./thinking-in-react/src/components/filtered-product-table/filtered-product-table.js 27:20-69\n @ ./thinking-in-react/src/index.js\n @ ../aik/lib/webpack/assets/react-entry-point.js\n @ multi ../aik/~/webpack-dev-server/client?http://localhost:4444/ ../aik/~/webpack/hot/dev-server.js ../aik/lib/webpack/assets/react-entry-point.js"
]
4 changes: 4 additions & 0 deletions src/utils/__test__/mock-data/syntax-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"./thinking-in-react/src/components/product-table/product-table.js\n\n\u001b[4m/Users/ssysoev/Development/aik-examples/thinking-in-react/src/components/product-table/product-table.js\u001b[24m\n \u001b[2m20:8\u001b[22m \u001b[31merror\u001b[39m Parsing error: Unexpected token <\n\n\u001b[31m\u001b[1m✖ 1 problem (1 error, 0 warnings)\n\u001b[22m\u001b[39m\n @ ./thinking-in-react/src/components/filtered-product-table/filtered-product-table.js 27:20-69\n @ ./thinking-in-react/src/index.js\n @ ../aik/lib/webpack/assets/react-entry-point.js\n @ multi ../aik/~/webpack-dev-server/client?http://localhost:4444/ ../aik/~/webpack/hot/dev-server.js ../aik/lib/webpack/assets/react-entry-point.js",
"./thinking-in-react/src/components/product-table/product-table.js\nModule build failed: SyntaxError: Unexpected token (20:7)\n\n\u001b[0m \u001b[90m 18 | \u001b[39m \u001b[36mreturn\u001b[39m (\n \u001b[90m 19 | \u001b[39m \u001b[33m<\u001b[39m\u001b[33mdiv\u001b[39m className\u001b[33m=\u001b[39m\u001b[32m\"productTable\"\u001b[39m\u001b[33m>\u001b[39m\n\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 20 | \u001b[39m \u001b[33m<<\u001b[39m\u001b[33mtable\u001b[39m\u001b[33m>\u001b[39m\n \u001b[90m | \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\n \u001b[90m 21 | \u001b[39m \u001b[33m<\u001b[39m\u001b[33mthead\u001b[39m\u001b[33m>\u001b[39m\n \u001b[90m 22 | \u001b[39m \u001b[33m<\u001b[39m\u001b[33mtr\u001b[39m\u001b[33m>\u001b[39m\n \u001b[90m 23 | \u001b[39m \u001b[33m<\u001b[39m\u001b[33mth\u001b[39m\u001b[33m>\u001b[39m\u001b[33mName\u001b[39m\u001b[33m<\u001b[39m\u001b[33m/\u001b[39m\u001b[33mth\u001b[39m\u001b[33m>\u001b[39m\u001b[0m\n\n @ ./thinking-in-react/src/components/filtered-product-table/filtered-product-table.js 27:20-69\n @ ./thinking-in-react/src/index.js\n @ ../aik/lib/webpack/assets/react-entry-point.js\n @ multi ../aik/~/webpack-dev-server/client?http://localhost:4444/ ../aik/~/webpack/hot/dev-server.js ../aik/lib/webpack/assets/react-entry-point.js"
]
120 changes: 100 additions & 20 deletions src/utils/error-helpers.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,118 @@
/* @flow */

import chalk from "chalk";

const SYNTAX_ERROR_LABEL = "SyntaxError:";
const SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY = "Syntax Error:";
const ESLINT_PARSE_ERROR = "Parsing error:";

/**
* Checks whether error is syntax error.
*/
export function isLikelyASyntaxError(message: string): boolean {
export function isSyntaxError(message: string): boolean {
return (
message.indexOf(SYNTAX_ERROR_LABEL) !== -1 ||
message.indexOf(SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY) !== -1
);
}

/**
* Makes some common errors shorter.
* Checks whether error is eslint rules warning.
*/
export function formatMessage(message: string): string {
return (
message
// Babel syntax error
.replace(
"Module build failed: SyntaxError:",
SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY
)
// Webpack file not found error
.replace(
/Module not found: Error: Cannot resolve 'file' or 'directory'/,
"Module not found:"
)
// Internal stacks are generally useless so we strip them
.replace(/^\s*at\s((?!webpack:).)*:\d+:\d+[\s\)]*(\n|$)/gm, "") // at ... ...:x:y
// Webpack loader names obscure CSS filenames
.replace("./~/css-loader!./~/postcss-loader!", "")
.replace(/\s@ multi .+/, "")
export function isEslintWarning(message: string): boolean {
return !!message.match(/\d\sproblem/) && !!message.match("warning");
}

/**
* Checks whether error is eslint parse error.
*/
export function isEslintParseError(message: string): boolean {
return !!message.match(ESLINT_PARSE_ERROR);
}

export function findMessagesToFormat(messages: string[]): string[] {
return messages.filter(message => {
if (isEslintParseError(message)) {
return false;
}

return true;
});
}

export function isFileSnippetLine(line: string): boolean {
return !!line.match(/\s+\|/);
}

export function isEslintWarningRuleLine(line: string): boolean {
// console.log(line, !!line.match(/\d+:\d+(.+)warning/));
return !!line.match(/\d+:\d+(.+)warning/);
}

export function getLinePadding(line: string): number {
return line.split("").findIndex(c => !!c.match(/\S/));
}

export function removeLinePadding(padding: number, line: string): string {
return line.substring(padding);
}

/**
* Formats "Syntax Error" message.
*/
export function formatSyntaxError(message: string): string {
const messageByLine = message.split("\n");
const padding = getLinePadding(messageByLine[0]);
const filePath = removeLinePadding(padding, messageByLine[0]);
const error = removeLinePadding(
padding,
messageByLine[1].replace(
"Module build failed: SyntaxError:",
SYNTAX_ERROR_LABEL_HUMAN_FRIENDLY
)
);
const snippet = messageByLine
.filter(isFileSnippetLine)
.map(removeLinePadding.bind(null, padding));

return `${chalk.red("Error:")}
${chalk.dim("|")} ${error}
${chalk.yellow("Where:")} ${filePath}
${snippet.join("\n")}`;
}

/**
* Formats "ESLint warning" message.
*/
export function formatEslintWarning(message: string): string {
const messageByLine = message.split("\n");
const padding = getLinePadding(messageByLine[0]);
const filePath = removeLinePadding(padding, messageByLine[0]);
const rule = messageByLine
.filter(isEslintWarningRuleLine)
.map(removeLinePadding.bind(null, padding + 2));

return `${rule.join("\n")}
${chalk.yellow("Where:")} ${filePath}`;
}

/**
* Beautifies error messages and warnings.
*/
export function formatMessages(messages: string[]): string[] {
return findMessagesToFormat(messages).map(message => {
if (isSyntaxError(message)) {
return formatSyntaxError(message);
}

if (isEslintWarning(message)) {
return formatEslintWarning(message);
}

return message;
});
}
Loading

0 comments on commit 7e33a6f

Please sign in to comment.