Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve unmapper file heuristic, add limited warning support, and ignore internal errors #2128

Merged
merged 23 commits into from
May 13, 2017
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-error-overlay/src/components/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function createOverlay(
applyStyles(header, headerStyle);

// Make message prettier
let finalMessage = message.match(/^\w*:/) ? name + ': ' + message : message;
let finalMessage = message.match(/^\w*:/) ? message : name + ': ' + message;
finalMessage = finalMessage
// TODO: maybe remove this prefix from fbjs?
// It's just scaring people
Expand Down
22 changes: 22 additions & 0 deletions packages/react-error-overlay/src/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import {
register as registerStackTraceLimit,
unregister as unregisterStackTraceLimit,
} from './effects/stackTraceLimit';
import {
permanentRegister as permanentRegisterConsole,
} from './effects/proxyConsole';
import { massage as massageWarning } from './utils/warnings';

import {
consume as consumeError,
Expand Down Expand Up @@ -156,6 +160,10 @@ function crash(error: Error, unhandledRejection = false) {
}
consumeError(error, unhandledRejection, CONTEXT_SIZE)
.then(ref => {
if (ref == null) {
console.warn('react-error-overlay ignored:', error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be careful not to leave messages that would be confusing to users. If one sees this message, is this a bug? Should they file an issue? Should the message say so?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be a better message?

react-error-overlay could not deduce if this error was from your code. Since we can't display anything useful, we're giving you this warning instead.
This error is most likely a bug with the package you see in the stack trace, however, it may be caused indirectly by something you are calling within the package.
Try searching or opening an issue with the relevant package.
${error}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Can’t we just rethrow the error?

Copy link
Contributor Author

@Timer Timer May 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is always thrown, we don't swallow it. We're intentionally not showing it -- so we need some message. With no message they might think it's a bug on our end (that the overlay didn't show up).

Copy link
Contributor

@gaearon gaearon May 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. I think in this case it's fine not to print anything extra. IMO let's silently leave the browser behavior in this case and see what people say.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay!

return;
}
errorReferences.push(ref);
if (iframeReference !== null && additionalReference !== null) {
updateAdditional(
Expand Down Expand Up @@ -205,6 +213,20 @@ function inject() {
registerPromise(window, error => crash(error, true));
registerShortcuts(window, shortcutHandler);
registerStackTraceLimit();

permanentRegisterConsole('error', warning => {
const data = massageWarning(warning);
if (data == null) return;
crash(
// $FlowFixMe
{
message: data.message,
stack: data.stack,
__unmap_source: '/static/js/bundle.js',
},
false
);
});
}

function uninject() {
Expand Down
9 changes: 8 additions & 1 deletion packages/react-error-overlay/src/utils/errorRegister.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function consume(
error: Error,
unhandledRejection: boolean = false,
contextSize: number = 3
): Promise<ErrorRecordReference> {
): Promise<ErrorRecordReference | null> {
const parsedFrames = parse(error);
let enhancedFramesPromise;
if (error.__unmap_source) {
Expand All @@ -33,6 +33,13 @@ function consume(
enhancedFramesPromise = map(parsedFrames, contextSize);
}
return enhancedFramesPromise.then(enhancedFrames => {
if (
enhancedFrames
.map(f => f._originalFileName)
.filter(f => f != null && f.indexOf('node_modules') === -1).length === 0
) {
return null;
}
enhancedFrames = enhancedFrames.filter(
({ functionName }) =>
functionName == null ||
Expand Down
47 changes: 28 additions & 19 deletions packages/react-error-overlay/src/utils/unmapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import { getSourceMap } from './getSourceMap';
import { getLinesAround } from './getLinesAround';
import path from 'path';

function count(search: string, string: string): number {
// Count starts at -1 becuse a do-while loop always runs at least once
let count = -1, index = -1;
do {
// First call or the while case evaluated true, meaning we have to make
// count 0 or we found a character
++count;
// Find the index of our search string, starting after the previous index
index = string.indexOf(search, index + 1);
} while (index !== -1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's better thanks!

return count;
}

/**
* Turns a set of mapped <code>StackFrame</code>s back into their generated code position and enhances them with code.
* @param {string} fileUri The URI of the <code>bundle.js</code> file.
Expand Down Expand Up @@ -39,28 +52,23 @@ async function unmap(
return frame;
}
const fN: string = fileName;
const splitCache1: any = {}, splitCache2: any = {}, splitCache3: any = {};
const source = map
.getSources()
.map(s => s.replace(/[\\]+/g, '/'))
.filter(s => {
s = path.normalize(s);
return s.indexOf(fN) === s.length - fN.length;
})
.sort((a, b) => {
let a2 = splitCache1[a] || (splitCache1[a] = a.split(path.sep)),
b2 = splitCache1[b] || (splitCache1[b] = b.split(path.sep));
return Math.sign(a2.length - b2.length);
})
.sort((a, b) => {
let a2 = splitCache2[a] || (splitCache2[a] = a.split('node_modules')),
b2 = splitCache2[b] || (splitCache2[b] = b.split('node_modules'));
return Math.sign(a2.length - b2.length);
.filter(p => {
p = path.normalize(p);
const i = p.lastIndexOf(fN);
return i !== -1 && i === p.length - fN.length;
})
.map(p => ({
token: p,
seps: count(path.sep, path.normalize(p)),
penalties: count('node_modules', p) + count('~', p),
}))
.sort((a, b) => {
let a2 = splitCache3[a] || (splitCache3[a] = a.split('~')),
b2 = splitCache3[b] || (splitCache3[b] = b.split('~'));
return Math.sign(a2.length - b2.length);
const s = Math.sign(a.seps - b.seps);
if (s !== 0) return s;
return Math.sign(a.penalties - b.penalties);
});
if (source.length < 1 || lineNumber == null) {
return new StackFrame(
Expand All @@ -76,13 +84,14 @@ async function unmap(
null
);
}
const sourceT = source[0].token;
const { line, column } = map.getGeneratedPosition(
source[0],
sourceT,
lineNumber,
// $FlowFixMe
columnNumber
);
const originalSource = map.getSource(source[0]);
const originalSource = map.getSource(sourceT);
return new StackFrame(
functionName,
fileUri,
Expand Down
43 changes: 43 additions & 0 deletions packages/react-error-overlay/src/utils/warnings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @flow

// This is a list of React warnings to display
// There must be zero or one capture group; and the capture group is assumed to be a missing stack frame.
const warnings = [
/^.*React.createElement: type is invalid.+Check your code at (.*?:.*)[.]$/,
];
// This is a list of information to remove from React warnings, it's not particularly useful to show
const removals = [/Check your code at (.*?:.*)[.]/];

function massage(warning: string): { message: string, stack: string } | null {
const nIndex = warning.indexOf('\n');
let message = warning;
if (nIndex !== -1) {
message = message.substring(0, nIndex);
}
let stack = warning.substring(nIndex + 1);

let found = false;
for (const warning of warnings) {
const m = message.match(warning);
if (!m) {
continue;
}
found = true;
if (!m[1]) {
break;
}
stack = `in (render function) (at ${m[1]})\n${stack}`;
break;
}
if (!found) {
return null;
}

for (const trim of removals) {
message = message.replace(trim, '');
}

return { message, stack };
}

export { massage };