mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-22 09:36:43 +03:00
Merge pull request #418 from reduxjs/trace-stack
This commit is contained in:
commit
fb9d826f61
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-eslint": "^10.0.0",
|
||||||
"lerna": "3.4.2"
|
"lerna": "3.4.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -50,7 +50,8 @@ export default function configureStore(initialState) {
|
||||||
- **pauseActionType** *string* - if specified, whenever `pauseRecording(false)` lifted action is dispatched and there are actions in the history log, will add this action type. If not specified, will commit when paused.
|
- **pauseActionType** *string* - if specified, whenever `pauseRecording(false)` lifted action is dispatched and there are actions in the history log, will add this action type. If not specified, will commit when paused.
|
||||||
- **shouldStartLocked** *boolean* - if specified as `true`, it will not allow any non-monitor actions to be dispatched till `lockChanges(false)` is dispatched. Default is `false`.
|
- **shouldStartLocked** *boolean* - if specified as `true`, it will not allow any non-monitor actions to be dispatched till `lockChanges(false)` is dispatched. Default is `false`.
|
||||||
- **shouldHotReload** *boolean* - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Default to `true`.
|
- **shouldHotReload** *boolean* - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Default to `true`.
|
||||||
- **shouldIncludeCallstack** *boolean* - if set to `true`, will include callstack for every dispatched action. Default to `false`.
|
- **trace** *boolean* or *function* - if set to `true`, will include stack trace for every dispatched action. You can use a function (with action object as argument) which should return `new Error().stack` string, getting the stack outside of reducers. Default to `false`.
|
||||||
|
- **traceLimit** *number* - maximum stack trace frames to be stored (in case `trace` option was provided as `true`). By default it's `10`. If `trace` option is a function, `traceLimit` will have no effect, that should be handled there like so: `trace: () => new Error().stack.split('\n').slice(0, limit+1).join('\n')` (`+1` is needed for Chrome where's an extra 1st frame for `Error\n`).
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redux-devtools-instrument",
|
"name": "redux-devtools-instrument",
|
||||||
"version": "1.9.3",
|
"version": "1.9.4",
|
||||||
"description": "Redux DevTools instrumentation",
|
"description": "Redux DevTools instrumentation",
|
||||||
"main": "lib/instrument.js",
|
"main": "lib/instrument.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const ActionTypes = {
|
||||||
* Action creators to change the History state.
|
* Action creators to change the History state.
|
||||||
*/
|
*/
|
||||||
export const ActionCreators = {
|
export const ActionCreators = {
|
||||||
performAction(action, shouldIncludeCallstack) {
|
performAction(action, trace, traceLimit, toExcludeFromTrace) {
|
||||||
if (!isPlainObject(action)) {
|
if (!isPlainObject(action)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Actions must be plain objects. ' +
|
'Actions must be plain objects. ' +
|
||||||
|
@ -38,10 +38,35 @@ export const ActionCreators = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
let stack;
|
||||||
type: ActionTypes.PERFORM_ACTION, action, timestamp: Date.now(),
|
if (trace) {
|
||||||
stack: shouldIncludeCallstack ? Error().stack : undefined
|
let extraFrames = 0;
|
||||||
};
|
if (typeof trace === 'function') {
|
||||||
|
stack = trace(action);
|
||||||
|
} else {
|
||||||
|
const error = Error();
|
||||||
|
let prevStackTraceLimit;
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
if (Error.stackTraceLimit < traceLimit) {
|
||||||
|
prevStackTraceLimit = Error.stackTraceLimit;
|
||||||
|
Error.stackTraceLimit = traceLimit;
|
||||||
|
}
|
||||||
|
Error.captureStackTrace(error, toExcludeFromTrace);
|
||||||
|
} else {
|
||||||
|
extraFrames = 3;
|
||||||
|
}
|
||||||
|
stack = error.stack;
|
||||||
|
if (prevStackTraceLimit) Error.stackTraceLimit = prevStackTraceLimit;
|
||||||
|
if (extraFrames || typeof Error.stackTraceLimit !== 'number' || Error.stackTraceLimit > traceLimit) {
|
||||||
|
const frames = stack.split('\n');
|
||||||
|
if (frames.length > traceLimit) {
|
||||||
|
stack = frames.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0)).join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: ActionTypes.PERFORM_ACTION, action, timestamp: Date.now(), stack };
|
||||||
},
|
},
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
@ -188,8 +213,8 @@ function recomputeStates(
|
||||||
/**
|
/**
|
||||||
* Lifts an app's action into an action on the lifted store.
|
* Lifts an app's action into an action on the lifted store.
|
||||||
*/
|
*/
|
||||||
export function liftAction(action, shouldIncludeCallstack) {
|
export function liftAction(action, trace, traceLimit, toExcludeFromTrace) {
|
||||||
return ActionCreators.performAction(action, shouldIncludeCallstack);
|
return ActionCreators.performAction(action, trace, traceLimit, toExcludeFromTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -502,7 +527,7 @@ export function liftReducerWith(reducer, initialCommittedState, monitorReducer,
|
||||||
minInvalidatedStateIndex = 0;
|
minInvalidatedStateIndex = 0;
|
||||||
// iterate through actions
|
// iterate through actions
|
||||||
liftedAction.nextLiftedState.forEach(action => {
|
liftedAction.nextLiftedState.forEach(action => {
|
||||||
actionsById[nextActionId] = liftAction(action, options.shouldIncludeCallstack);
|
actionsById[nextActionId] = liftAction(action, options.trace || options.shouldIncludeCallstack);
|
||||||
stagedActionIds.push(nextActionId);
|
stagedActionIds.push(nextActionId);
|
||||||
nextActionId++;
|
nextActionId++;
|
||||||
});
|
});
|
||||||
|
@ -595,7 +620,8 @@ export function unliftState(liftedState) {
|
||||||
*/
|
*/
|
||||||
export function unliftStore(liftedStore, liftReducer, options) {
|
export function unliftStore(liftedStore, liftReducer, options) {
|
||||||
let lastDefinedState;
|
let lastDefinedState;
|
||||||
const { shouldIncludeCallstack } = options;
|
const trace = options.trace || options.shouldIncludeCallstack;
|
||||||
|
const traceLimit = options.traceLimit || 10;
|
||||||
|
|
||||||
function getState() {
|
function getState() {
|
||||||
const state = unliftState(liftedStore.getState());
|
const state = unliftState(liftedStore.getState());
|
||||||
|
@ -605,15 +631,17 @@ export function unliftStore(liftedStore, liftReducer, options) {
|
||||||
return lastDefinedState;
|
return lastDefinedState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dispatch(action) {
|
||||||
|
liftedStore.dispatch(liftAction(action, trace, traceLimit, dispatch));
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...liftedStore,
|
...liftedStore,
|
||||||
|
|
||||||
liftedStore,
|
liftedStore,
|
||||||
|
|
||||||
dispatch(action) {
|
dispatch,
|
||||||
liftedStore.dispatch(liftAction(action, shouldIncludeCallstack));
|
|
||||||
return action;
|
|
||||||
},
|
|
||||||
|
|
||||||
getState,
|
getState,
|
||||||
|
|
||||||
|
|
|
@ -686,6 +686,178 @@ describe('instrument', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('trace option', () => {
|
||||||
|
let monitoredStore;
|
||||||
|
let monitoredLiftedStore;
|
||||||
|
let exportedState;
|
||||||
|
|
||||||
|
it('should not include stack trace', () => {
|
||||||
|
monitoredStore = createStore(counter, instrument());
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include stack trace', () => {
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: true }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/^Error/);
|
||||||
|
expect(exportedState.actionsById[1].stack).toNotMatch(/instrument.js/);
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
|
||||||
|
expect(exportedState.actionsById[1].stack.split('\n').length).toBe(10 + 1); // +1 is for `Error\n`
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include only 3 frames for stack trace', () => {
|
||||||
|
function fn1() {
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: true, traceLimit: 3 }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn1 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn2 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn3 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toNotMatch(/at fn4 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack.split('\n').length).toBe(3 + 1);
|
||||||
|
}
|
||||||
|
function fn2() { return fn1(); }
|
||||||
|
function fn3() { return fn2(); }
|
||||||
|
function fn4() { return fn3(); }
|
||||||
|
fn4();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should force traceLimit value of 3 when Error.stackTraceLimit is 10', () => {
|
||||||
|
const stackTraceLimit = Error.stackTraceLimit;
|
||||||
|
Error.stackTraceLimit = 10;
|
||||||
|
function fn1() {
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: true, traceLimit: 3 }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn1 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn2 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn3 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toNotMatch(/at fn4 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack.split('\n').length).toBe(3 + 1);
|
||||||
|
}
|
||||||
|
function fn2() { return fn1(); }
|
||||||
|
function fn3() { return fn2(); }
|
||||||
|
function fn4() { return fn3(); }
|
||||||
|
fn4();
|
||||||
|
Error.stackTraceLimit = stackTraceLimit;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should force traceLimit value of 5 even when Error.stackTraceLimit is 2', () => {
|
||||||
|
const stackTraceLimit = Error.stackTraceLimit;
|
||||||
|
Error.stackTraceLimit = 2;
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: true, traceLimit: 5 }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
Error.stackTraceLimit = stackTraceLimit;
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/^Error/);
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
|
||||||
|
expect(exportedState.actionsById[1].stack.split('\n').length).toBe(5 + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should force default limit of 10 even when Error.stackTraceLimit is 3', () => {
|
||||||
|
const stackTraceLimit = Error.stackTraceLimit;
|
||||||
|
Error.stackTraceLimit = 3;
|
||||||
|
function fn1() {
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: true }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
Error.stackTraceLimit = stackTraceLimit;
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn1 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn2 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn3 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/at fn4 /);
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack.split('\n').length).toBe(10 + 1);
|
||||||
|
}
|
||||||
|
function fn2() { return fn1(); }
|
||||||
|
function fn3() { return fn2(); }
|
||||||
|
function fn4() { return fn3(); }
|
||||||
|
fn4();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include 3 extra frames when Error.captureStackTrace not suported', () => {
|
||||||
|
const captureStackTrace = Error.captureStackTrace;
|
||||||
|
Error.captureStackTrace = undefined;
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: true, traceLimit: 5 }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
Error.captureStackTrace = captureStackTrace;
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toMatch(/^Error/);
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
|
||||||
|
expect(exportedState.actionsById[1].stack.split('\n').length).toBe(5 + 3 + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get stack trace from a function', () => {
|
||||||
|
const traceFn = () => new Error().stack;
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: traceFn }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('at Object.performAction');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get stack trace inside setTimeout using a function', (done) => {
|
||||||
|
const stack = new Error().stack;
|
||||||
|
setTimeout(() => {
|
||||||
|
const traceFn = () => stack + new Error().stack;
|
||||||
|
monitoredStore = createStore(counter, instrument(undefined, { trace: traceFn }));
|
||||||
|
monitoredLiftedStore = monitoredStore.liftedStore;
|
||||||
|
monitoredStore.dispatch({ type: 'INCREMENT' });
|
||||||
|
|
||||||
|
exportedState = monitoredLiftedStore.getState();
|
||||||
|
expect(exportedState.actionsById[0].stack).toBe(undefined);
|
||||||
|
expect(exportedState.actionsById[1].stack).toBeA('string');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('at Object.performAction');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
|
||||||
|
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Import State', () => {
|
describe('Import State', () => {
|
||||||
let monitoredStore;
|
let monitoredStore;
|
||||||
let monitoredLiftedStore;
|
let monitoredLiftedStore;
|
||||||
|
@ -736,8 +908,8 @@ describe('instrument', () => {
|
||||||
expect(importMonitoredLiftedStore.getState()).toEqual(expectedImportedState);
|
expect(importMonitoredLiftedStore.getState()).toEqual(expectedImportedState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include callstack', () => {
|
it('should include stack trace', () => {
|
||||||
let importMonitoredStore = createStore(counter, instrument(undefined, { shouldIncludeCallstack: true }));
|
let importMonitoredStore = createStore(counter, instrument(undefined, { trace: true }));
|
||||||
let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
|
let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
|
||||||
|
|
||||||
importMonitoredStore.dispatch({ type: 'DECREMENT' });
|
importMonitoredStore.dispatch({ type: 'DECREMENT' });
|
||||||
|
@ -801,8 +973,8 @@ describe('instrument', () => {
|
||||||
expect(filterStackAndTimestamps(importMonitoredLiftedStore.getState())).toEqual(exportedState);
|
expect(filterStackAndTimestamps(importMonitoredLiftedStore.getState())).toEqual(exportedState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include callstack', () => {
|
it('should include stack trace', () => {
|
||||||
let importMonitoredStore = createStore(counter, instrument(undefined, { shouldIncludeCallstack: true }));
|
let importMonitoredStore = createStore(counter, instrument(undefined, { trace: true }));
|
||||||
let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
|
let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
|
||||||
|
|
||||||
importMonitoredStore.dispatch({ type: 'DECREMENT' });
|
importMonitoredStore.dispatch({ type: 'DECREMENT' });
|
||||||
|
|
13
packages/redux-devtools-trace-monitor/.babelrc
Normal file
13
packages/redux-devtools-trace-monitor/.babelrc
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"presets": [ ["env", { "modules": "commonjs" }], "react", "flow" ],
|
||||||
|
"plugins": [
|
||||||
|
["transform-runtime", {
|
||||||
|
"polyfill": false,
|
||||||
|
"regenerator": true
|
||||||
|
}],
|
||||||
|
"transform-class-properties",
|
||||||
|
"transform-object-rest-spread",
|
||||||
|
"add-module-exports",
|
||||||
|
"transform-decorators-legacy"
|
||||||
|
]
|
||||||
|
}
|
5
packages/redux-devtools-trace-monitor/.eslintignore
Normal file
5
packages/redux-devtools-trace-monitor/.eslintignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
build
|
||||||
|
dev
|
||||||
|
dist
|
||||||
|
lib
|
34
packages/redux-devtools-trace-monitor/.eslintrc
Normal file
34
packages/redux-devtools-trace-monitor/.eslintrc
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"extends": "plugin:flowtype/recommended",
|
||||||
|
"globals": {
|
||||||
|
"chrome": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"jest": true,
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-uses-react": 2,
|
||||||
|
"react/jsx-uses-vars": 2,
|
||||||
|
"react/react-in-jsx-scope": 2,
|
||||||
|
"react/sort-comp": 0,
|
||||||
|
"react/jsx-quotes": 0,
|
||||||
|
"eol-last": 0,
|
||||||
|
"no-unused-vars": 0,
|
||||||
|
"no-console": 1,
|
||||||
|
"comma-dangle": 0
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"flowtype"
|
||||||
|
]
|
||||||
|
}
|
19
packages/redux-devtools-trace-monitor/LICENSE
Normal file
19
packages/redux-devtools-trace-monitor/LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
36
packages/redux-devtools-trace-monitor/README.md
Normal file
36
packages/redux-devtools-trace-monitor/README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Redux DevTools Stack Trace Monitor
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Submonitor for Redux DevTools inspector to show stack traces. Based on [`react-error-overlay`](https://github.com/facebook/create-react-app/tree/master/packages/react-error-overlay) and the contribution of [Mark Erikson](https://github.com/markerikson) in [the PR from `remotedev-app`](https://github.com/zalmoxisus/remotedev-app/pull/43/).
|
||||||
|
|
||||||
|
It's integrated in Redux DevTools browser extension. To use it separately with [`redux-devtools`](https://github.com/reduxjs/redux-devtools/packages/redux-devtools) and [`redux-devtools-inspector`](https://github.com/reduxjs/redux-devtools/packages/redux-devtools-inspector) according to [Walkthrough](https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md):
|
||||||
|
|
||||||
|
##### `containers/DevTools.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import React from 'react';
|
||||||
|
import { createDevTools } from 'redux-devtools';
|
||||||
|
import Inspector from 'redux-devtools-inspector';
|
||||||
|
import TraceMonitor from 'redux-devtools-trace-monitor';
|
||||||
|
|
||||||
|
export default createDevTools(
|
||||||
|
<Inspector
|
||||||
|
tabs: defaultTabs => [...defaultTabs, { name: 'Trace', component: TraceMonitor }]
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
##### `store/configureStore.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ...
|
||||||
|
const enhancer = compose(
|
||||||
|
// ...
|
||||||
|
DevTools.instrument({ trace: true })
|
||||||
|
);
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
MIT
|
63
packages/redux-devtools-trace-monitor/package.json
Normal file
63
packages/redux-devtools-trace-monitor/package.json
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "redux-devtools-trace-monitor",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Submonitor for Redux DevTools inspector to show stack traces.",
|
||||||
|
"repository": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"homepage": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"author": "Mark Erikson <mark@isquaredsoftware.com>",
|
||||||
|
"contributors": [
|
||||||
|
"Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "lib/StackTraceTab.js",
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"build": "babel src --out-dir lib",
|
||||||
|
"lint": "eslint src test",
|
||||||
|
"lint:fix": "eslint --fix src test",
|
||||||
|
"test": "jest --no-cache",
|
||||||
|
"prepare": "npm run clean && npm run build",
|
||||||
|
"prepublishOnly": "npm run lint && npm run test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.10.1",
|
||||||
|
"babel-eslint": "^10.0.0",
|
||||||
|
"babel-loader": "^6.2.4",
|
||||||
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
|
"babel-plugin-react-transform": "^2.0.0",
|
||||||
|
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"babel-preset-es2015": "^6.9.0",
|
||||||
|
"babel-preset-es2015-loose": "^7.0.0",
|
||||||
|
"babel-preset-react": "^6.5.0",
|
||||||
|
"babel-preset-react-app": "^3.1.2",
|
||||||
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
|
"babel-register": "^6.11.6",
|
||||||
|
"babel-runtime": "^6.23.0",
|
||||||
|
"enzyme": "^3.0.0",
|
||||||
|
"enzyme-adapter-react-15": "1.2.0",
|
||||||
|
"enzyme-to-json": "^3.3.0",
|
||||||
|
"eslint": "^5.0.0",
|
||||||
|
"eslint-plugin-flowtype": "3.2.0",
|
||||||
|
"eslint-plugin-import": "2.14.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "6.1.1",
|
||||||
|
"eslint-plugin-react": "7.11.1",
|
||||||
|
"jest": "^23.6.0",
|
||||||
|
"react-addons-test-utils": "^15.4.0",
|
||||||
|
"react-dom": "^15.4.0",
|
||||||
|
"react-test-renderer": "^15.3.2",
|
||||||
|
"rimraf": "^2.5.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"anser": "^1.4.7",
|
||||||
|
"chalk": "^2.4.1",
|
||||||
|
"html-entities": "^1.2.1",
|
||||||
|
"react": "^15.4.0",
|
||||||
|
"redux-devtools-themes": "^1.0.0",
|
||||||
|
"settle-promise": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
114
packages/redux-devtools-trace-monitor/src/StackTraceTab.js
Normal file
114
packages/redux-devtools-trace-monitor/src/StackTraceTab.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import {getStackFrames} from './react-error-overlay/utils/getStackFrames';
|
||||||
|
import StackTrace from './react-error-overlay/containers/StackTrace';
|
||||||
|
import openFile from './openFile';
|
||||||
|
|
||||||
|
const rootStyle = {padding: '5px 10px'};
|
||||||
|
|
||||||
|
export default class StackTraceTab extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
stackFrames: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
// console.log("StackTraceTab mounted");
|
||||||
|
this.checkForStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {action, actions} = prevProps;
|
||||||
|
|
||||||
|
if(action !== this.props.action || actions !== this.props.actions) {
|
||||||
|
this.checkForStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForStackTrace() {
|
||||||
|
const {action, actions: liftedActionsById} = this.props;
|
||||||
|
|
||||||
|
if(!action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const liftedActions = Object.values(liftedActionsById);
|
||||||
|
const liftedAction = liftedActions.find(liftedAction => liftedAction.action === action);
|
||||||
|
|
||||||
|
if(liftedAction && typeof liftedAction.stack === 'string') {
|
||||||
|
const deserializedError = Object.assign(new Error(), {stack: liftedAction.stack});
|
||||||
|
|
||||||
|
getStackFrames(deserializedError)
|
||||||
|
.then(stackFrames => {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
if (process.env.NODE_ENV === 'development') console.log('Stack frames: ', stackFrames);
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
this.setState({stackFrames, currentError: deserializedError});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({
|
||||||
|
stackFrames: [],
|
||||||
|
showDocsLink: liftedAction.action && liftedAction.action.type && liftedAction.action.type !== '@@INIT'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onStackLocationClicked = (fileLocation = {}) => {
|
||||||
|
// console.log("Stack location args: ", ...args);
|
||||||
|
|
||||||
|
const {fileName, lineNumber} = fileLocation;
|
||||||
|
|
||||||
|
if(fileName && lineNumber) {
|
||||||
|
const matchingStackFrame = this.state.stackFrames.find(stackFrame => {
|
||||||
|
const matches = (
|
||||||
|
(stackFrame._originalFileName === fileName && stackFrame._originalLineNumber === lineNumber) ||
|
||||||
|
(stackFrame.fileName === fileName && stackFrame.lineNumber === lineNumber)
|
||||||
|
);
|
||||||
|
return matches;
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("Matching stack frame: ", matchingStackFrame);
|
||||||
|
|
||||||
|
if(matchingStackFrame) {
|
||||||
|
/*
|
||||||
|
const frameIndex = this.state.stackFrames.indexOf(matchingStackFrame);
|
||||||
|
const originalStackFrame = parsedFramesNoSourcemaps[frameIndex];
|
||||||
|
console.log("Original stack frame: ", originalStackFrame);
|
||||||
|
*/
|
||||||
|
openFile(fileName, lineNumber, matchingStackFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openDocs = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.open('https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Features/Trace.md');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {stackFrames, showDocsLink} = this.state;
|
||||||
|
|
||||||
|
if (showDocsLink) {
|
||||||
|
return (
|
||||||
|
<div style={rootStyle}>
|
||||||
|
To enable tracing action calls, you should set `trace` option to `true` for Redux DevTools enhancer.
|
||||||
|
Refer to <a href="#" onClick={this.openDocs}>this page</a> for more details.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={rootStyle}>
|
||||||
|
<StackTrace
|
||||||
|
stackFrames={stackFrames}
|
||||||
|
errorName={"N/A"}
|
||||||
|
contextSize={3}
|
||||||
|
editorHandler={this.onStackLocationClicked}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
92
packages/redux-devtools-trace-monitor/src/openFile.js
Normal file
92
packages/redux-devtools-trace-monitor/src/openFile.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
const isFF = navigator.userAgent.indexOf('Firefox') !== -1;
|
||||||
|
|
||||||
|
function openResource(fileName, lineNumber, stackFrame) {
|
||||||
|
const adjustedLineNumber = Math.max(lineNumber - 1, 0);
|
||||||
|
chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (result) => {
|
||||||
|
//console.log("openResource callback args: ", callbackArgs);
|
||||||
|
if(result.isError) {
|
||||||
|
const {fileName: finalFileName, lineNumber: finalLineNumber} = stackFrame;
|
||||||
|
const adjustedLineNumber = Math.max(finalLineNumber - 1, 0);
|
||||||
|
chrome.devtools.panels.openResource(finalFileName, adjustedLineNumber, (result) => {
|
||||||
|
// console.log("openResource result: ", result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAndCloseTab(url) {
|
||||||
|
chrome.tabs.create({ url }, tab => {
|
||||||
|
const removeTab = () => {
|
||||||
|
chrome.windows.onFocusChanged.removeListener(removeTab);
|
||||||
|
if (tab && tab.id) {
|
||||||
|
chrome.tabs.remove(tab.id, () => {
|
||||||
|
if(chrome.runtime.lastError) console.log(chrome.runtime.lastError); // eslint-disable-line no-console
|
||||||
|
else if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||||
|
chrome.tabs.update(chrome.devtools.inspectedWindow.tabId, {active: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (chrome.windows) chrome.windows.onFocusChanged.addListener(removeTab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openInIframe(url) {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = url;
|
||||||
|
iframe.style = 'display:none';
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
setTimeout(() => iframe.parentNode.removeChild(iframe), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openInEditor(editor, path, stackFrame) {
|
||||||
|
const projectPath = path.replace(/\/$/, '');
|
||||||
|
const file = stackFrame._originalFileName || stackFrame.finalFileName || stackFrame.fileName || '';
|
||||||
|
let filePath = /^https?:\/\//.test(file) ? file.replace(/^https?:\/\/[^\/]*/, '') : file.replace(/^\w+:\/\//, '');
|
||||||
|
filePath = filePath.replace(/^\/~\//, '/node_modules/');
|
||||||
|
const line = stackFrame._originalLineNumber || stackFrame.lineNumber || '0';
|
||||||
|
const column = stackFrame._originalColumnNumber || stackFrame.columnNumber || '0';
|
||||||
|
let url;
|
||||||
|
|
||||||
|
switch (editor) {
|
||||||
|
case 'vscode': case 'code':
|
||||||
|
url = `vscode://file/${projectPath}${filePath}:${line}:${column}`; break;
|
||||||
|
case 'atom':
|
||||||
|
url = `atom://core/open/file?filename=${projectPath}${filePath}&line=${line}&column=${column}`; break;
|
||||||
|
case 'webstorm': case 'phpstorm': case 'idea':
|
||||||
|
url = `${editor}://open?file=${projectPath}${filePath}&line=${line}&column=${column}`; break;
|
||||||
|
default: // sublime, emacs, macvim, textmate + custom like https://github.com/eclemens/atom-url-handler
|
||||||
|
url = `${editor}://open/?url=file://${projectPath}${filePath}&line=${line}&column=${column}`;
|
||||||
|
}
|
||||||
|
if (process.env.NODE_ENV === 'development') console.log(url); // eslint-disable-line no-console
|
||||||
|
if (chrome.devtools && !isFF) {
|
||||||
|
if (chrome.tabs) openAndCloseTab(url);
|
||||||
|
else window.open(url);
|
||||||
|
} else {
|
||||||
|
openInIframe(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function openFile(fileName, lineNumber, stackFrame) {
|
||||||
|
if (process.env.NODE_ENV === 'development') console.log(fileName, lineNumber, stackFrame); // eslint-disable-line no-console
|
||||||
|
if (!chrome || !chrome.storage) return; // TODO: Pass editor settings for using outside of browser extension
|
||||||
|
const storage = isFF ? chrome.storage.local : chrome.storage.sync || chrome.storage.local;
|
||||||
|
storage.get(['useEditor', 'editor', 'projectPath'], function({ useEditor, editor, projectPath }) {
|
||||||
|
if (useEditor && projectPath && typeof editor === 'string' && /^\w{1,30}$/.test(editor)) {
|
||||||
|
openInEditor(editor.toLowerCase(), projectPath, stackFrame);
|
||||||
|
} else {
|
||||||
|
if (chrome.devtools && chrome.devtools.panels && chrome.devtools.panels.openResource) {
|
||||||
|
openResource(fileName, lineNumber, stackFrame);
|
||||||
|
} else if (chrome.runtime && (chrome.runtime.openOptionsPage || isFF)) {
|
||||||
|
if (chrome.devtools && isFF) {
|
||||||
|
chrome.devtools.inspectedWindow.eval('confirm("Set the editor to open the file in?")', result => {
|
||||||
|
if (!result) return;
|
||||||
|
chrome.runtime.sendMessage({ type: 'OPEN_OPTIONS' });
|
||||||
|
});
|
||||||
|
} else if (confirm('Set the editor to open the file in?')) {
|
||||||
|
chrome.runtime.openOptionsPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
1
packages/redux-devtools-trace-monitor/src/presets.js
Normal file
1
packages/redux-devtools-trace-monitor/src/presets.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const toExclude = /chrome-extension:\/\/|moz-extension:\/\//;
|
42
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/CodeBlock.js
vendored
Normal file
42
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/CodeBlock.js
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const preStyle = {
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
backgroundColor: '#000',
|
||||||
|
padding: '0.5em',
|
||||||
|
marginTop: '0.5em',
|
||||||
|
marginBottom: '0.5em',
|
||||||
|
overflowX: 'auto',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const codeStyle = {
|
||||||
|
fontFamily: 'Consolas, Menlo, monospace',
|
||||||
|
};
|
||||||
|
|
||||||
|
type CodeBlockPropsType = {|
|
||||||
|
main: boolean,
|
||||||
|
codeHTML: string,
|
||||||
|
|};
|
||||||
|
|
||||||
|
function CodeBlock(props: CodeBlockPropsType) {
|
||||||
|
const codeBlock = { __html: props.codeHTML };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<pre style={preStyle}>
|
||||||
|
<code style={codeStyle} dangerouslySetInnerHTML={codeBlock} />
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CodeBlock;
|
89
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/Collapsible.js
vendored
Normal file
89
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/Collapsible.js
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { nicinabox as theme } from 'redux-devtools-themes';
|
||||||
|
|
||||||
|
import type { Element as ReactElement } from 'react';
|
||||||
|
|
||||||
|
const _collapsibleStyle = {
|
||||||
|
color: theme.base06,
|
||||||
|
backgroundColor: theme.base01,
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: 'none',
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: '1em',
|
||||||
|
padding: '0px 5px',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
};
|
||||||
|
|
||||||
|
const collapsibleCollapsedStyle = {
|
||||||
|
..._collapsibleStyle,
|
||||||
|
marginBottom: '1.5em',
|
||||||
|
};
|
||||||
|
|
||||||
|
const collapsibleExpandedStyle = {
|
||||||
|
..._collapsibleStyle,
|
||||||
|
marginBottom: '0.6em',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
children: ReactElement<any>[],
|
||||||
|
|};
|
||||||
|
|
||||||
|
type State = {|
|
||||||
|
collapsed: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
class Collapsible extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
collapsed: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleCollapsed = () => {
|
||||||
|
this.setState(state => ({
|
||||||
|
collapsed: !this.isCollapsed(state),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
isCollapsed = (state) => (
|
||||||
|
state.collapsed === undefined ? this.props.collapsedByDefault : state.collapsed
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const count = this.props.children.length;
|
||||||
|
const collapsed = this.isCollapsed(this.state);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={this.toggleCollapsed}
|
||||||
|
style={
|
||||||
|
collapsed ? collapsibleCollapsedStyle : collapsibleExpandedStyle
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(collapsed ? '▶' : '▼') +
|
||||||
|
` ${count} stack frames were ` +
|
||||||
|
(collapsed ? 'collapsed.' : 'expanded.')}
|
||||||
|
</button>
|
||||||
|
<div style={{ display: collapsed ? 'none' : 'block' }}>
|
||||||
|
{this.props.children}
|
||||||
|
<button
|
||||||
|
onClick={this.toggleCollapsed}
|
||||||
|
style={collapsibleExpandedStyle}
|
||||||
|
>
|
||||||
|
{`▲ ${count} stack frames were expanded.`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Collapsible;
|
191
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrame.js
vendored
Normal file
191
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrame.js
vendored
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import CodeBlock from './StackFrameCodeBlock';
|
||||||
|
import { getPrettyURL } from '../utils/getPrettyURL';
|
||||||
|
import { nicinabox as theme } from 'redux-devtools-themes';
|
||||||
|
|
||||||
|
import type { StackFrame as StackFrameType } from '../utils/stack-frame';
|
||||||
|
import type { ErrorLocation } from '../utils/parseCompileError';
|
||||||
|
|
||||||
|
const linkStyle = {
|
||||||
|
fontSize: '0.9em',
|
||||||
|
marginBottom: '0.9em',
|
||||||
|
};
|
||||||
|
|
||||||
|
const anchorStyle = {
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: theme.base05,
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const codeAnchorStyle = {
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleStyle = {
|
||||||
|
marginBottom: '1.5em',
|
||||||
|
color: theme.base05,
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: 'none',
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'left',
|
||||||
|
background: 'transparent',
|
||||||
|
fontFamily: 'Consolas, Menlo, monospace',
|
||||||
|
fontSize: '1em',
|
||||||
|
padding: '0px',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
frame: StackFrameType,
|
||||||
|
contextSize: number,
|
||||||
|
critical: boolean,
|
||||||
|
showCode: boolean,
|
||||||
|
editorHandler: (errorLoc: ErrorLocation) => void,
|
||||||
|
|};
|
||||||
|
|
||||||
|
type State = {|
|
||||||
|
compiled: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
class StackFrame extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
compiled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleCompiled = () => {
|
||||||
|
this.setState(state => ({
|
||||||
|
compiled: !state.compiled,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
getErrorLocation(): ErrorLocation | null {
|
||||||
|
const {
|
||||||
|
_originalFileName: fileName,
|
||||||
|
_originalLineNumber: lineNumber,
|
||||||
|
} = this.props.frame;
|
||||||
|
// Unknown file
|
||||||
|
if (!fileName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// e.g. "/path-to-my-app/webpack/bootstrap eaddeb46b67d75e4dfc1"
|
||||||
|
const isInternalWebpackBootstrapCode = fileName.trim().indexOf(' ') !== -1;
|
||||||
|
if (isInternalWebpackBootstrapCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Code is in a real file
|
||||||
|
return { fileName, lineNumber: lineNumber || 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
editorHandler = () => {
|
||||||
|
const errorLoc = this.getErrorLocation();
|
||||||
|
if (!errorLoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.editorHandler(errorLoc);
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (e: SyntheticKeyboardEvent<>) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.editorHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { frame, contextSize, critical, showCode } = this.props;
|
||||||
|
const {
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
_scriptCode: scriptLines,
|
||||||
|
_originalFileName: sourceFileName,
|
||||||
|
_originalLineNumber: sourceLineNumber,
|
||||||
|
_originalColumnNumber: sourceColumnNumber,
|
||||||
|
_originalScriptCode: sourceLines,
|
||||||
|
} = frame;
|
||||||
|
const functionName = frame.getFunctionName();
|
||||||
|
|
||||||
|
const compiled = this.state.compiled;
|
||||||
|
const url = getPrettyURL(
|
||||||
|
sourceFileName,
|
||||||
|
sourceLineNumber,
|
||||||
|
sourceColumnNumber,
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
compiled
|
||||||
|
);
|
||||||
|
|
||||||
|
let codeBlockProps = null;
|
||||||
|
if (showCode) {
|
||||||
|
if (
|
||||||
|
compiled &&
|
||||||
|
scriptLines &&
|
||||||
|
scriptLines.length !== 0 &&
|
||||||
|
lineNumber != null
|
||||||
|
) {
|
||||||
|
codeBlockProps = {
|
||||||
|
lines: scriptLines,
|
||||||
|
lineNum: lineNumber,
|
||||||
|
columnNum: columnNumber,
|
||||||
|
contextSize,
|
||||||
|
main: critical,
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
!compiled &&
|
||||||
|
sourceLines &&
|
||||||
|
sourceLines.length !== 0 &&
|
||||||
|
sourceLineNumber != null
|
||||||
|
) {
|
||||||
|
codeBlockProps = {
|
||||||
|
lines: sourceLines,
|
||||||
|
lineNum: sourceLineNumber,
|
||||||
|
columnNum: sourceColumnNumber,
|
||||||
|
contextSize,
|
||||||
|
main: critical,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canOpenInEditor =
|
||||||
|
this.getErrorLocation() !== null && this.props.editorHandler !== null;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{functionName}</div>
|
||||||
|
<div style={linkStyle}>
|
||||||
|
<span
|
||||||
|
style={canOpenInEditor ? anchorStyle : null}
|
||||||
|
onClick={canOpenInEditor ? this.editorHandler : null}
|
||||||
|
onKeyDown={canOpenInEditor ? this.onKeyDown : null}
|
||||||
|
tabIndex={canOpenInEditor ? '0' : null}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{codeBlockProps && (
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
onClick={canOpenInEditor ? this.editorHandler : null}
|
||||||
|
style={canOpenInEditor ? codeAnchorStyle : null}
|
||||||
|
>
|
||||||
|
<CodeBlock {...codeBlockProps} />
|
||||||
|
</span>
|
||||||
|
<button style={toggleStyle} onClick={this.toggleCompiled}>
|
||||||
|
{'View ' + (compiled ? 'source' : 'compiled')}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackFrame;
|
102
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrameCodeBlock.js
vendored
Normal file
102
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackFrameCodeBlock.js
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React from 'react';
|
||||||
|
import CodeBlock from '../components/CodeBlock';
|
||||||
|
import { applyStyles } from '../utils/dom/css';
|
||||||
|
import { absolutifyCaret } from '../utils/dom/absolutifyCaret';
|
||||||
|
import type { ScriptLine } from '../utils/stack-frame';
|
||||||
|
import generateAnsiHTML from '../utils/generateAnsiHTML';
|
||||||
|
|
||||||
|
import { codeFrameColumns } from '@babel/code-frame';
|
||||||
|
import { nicinabox as theme } from 'redux-devtools-themes';
|
||||||
|
|
||||||
|
type StackFrameCodeBlockPropsType = {|
|
||||||
|
lines: ScriptLine[],
|
||||||
|
lineNum: number,
|
||||||
|
columnNum: ?number,
|
||||||
|
contextSize: number,
|
||||||
|
main: boolean,
|
||||||
|
|};
|
||||||
|
|
||||||
|
// Exact type workaround for spread operator.
|
||||||
|
// See: https://github.com/facebook/flow/issues/2405
|
||||||
|
type Exact<T> = $Shape<T>;
|
||||||
|
|
||||||
|
function StackFrameCodeBlock(props: Exact<StackFrameCodeBlockPropsType>) {
|
||||||
|
const { lines, lineNum, columnNum, contextSize, main } = props;
|
||||||
|
const sourceCode = [];
|
||||||
|
let whiteSpace = Infinity;
|
||||||
|
lines.forEach(function(e) {
|
||||||
|
const { content: text } = e;
|
||||||
|
const m = text.match(/^\s*/);
|
||||||
|
if (text === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m && m[0]) {
|
||||||
|
whiteSpace = Math.min(whiteSpace, m[0].length);
|
||||||
|
} else {
|
||||||
|
whiteSpace = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
lines.forEach(function(e) {
|
||||||
|
let { content: text } = e;
|
||||||
|
const { lineNumber: line } = e;
|
||||||
|
|
||||||
|
if (isFinite(whiteSpace)) {
|
||||||
|
text = text.substring(whiteSpace);
|
||||||
|
}
|
||||||
|
sourceCode[line - 1] = text;
|
||||||
|
});
|
||||||
|
const ansiHighlight = codeFrameColumns(
|
||||||
|
sourceCode.join('\n'),
|
||||||
|
{
|
||||||
|
start: {
|
||||||
|
line: lineNum,
|
||||||
|
column:
|
||||||
|
columnNum == null
|
||||||
|
? 0
|
||||||
|
: columnNum - (isFinite(whiteSpace) ? whiteSpace : 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
forceColor: true,
|
||||||
|
linesAbove: contextSize,
|
||||||
|
linesBelow: contextSize,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const htmlHighlight = generateAnsiHTML(ansiHighlight);
|
||||||
|
const code = document.createElement('code');
|
||||||
|
code.innerHTML = htmlHighlight;
|
||||||
|
absolutifyCaret(code);
|
||||||
|
|
||||||
|
const ccn = code.childNodes;
|
||||||
|
// eslint-disable-next-line
|
||||||
|
oLoop: for (let index = 0; index < ccn.length; ++index) {
|
||||||
|
const node = ccn[index];
|
||||||
|
const ccn2 = node.childNodes;
|
||||||
|
for (let index2 = 0; index2 < ccn2.length; ++index2) {
|
||||||
|
const lineNode = ccn2[index2];
|
||||||
|
const text = lineNode.innerText;
|
||||||
|
if (text == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (text.indexOf(' ' + lineNum + ' |') === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// $FlowFixMe
|
||||||
|
applyStyles(node, {'background-color': main ? theme.base02 : theme.base01});
|
||||||
|
// eslint-disable-next-line
|
||||||
|
break oLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CodeBlock main={main} codeHTML={code.innerHTML} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackFrameCodeBlock;
|
99
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackTrace.js
vendored
Normal file
99
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackTrace.js
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import StackFrame from './StackFrame';
|
||||||
|
import Collapsible from '../components/Collapsible';
|
||||||
|
import { isInternalFile } from '../utils/isInternalFile';
|
||||||
|
import { isBultinErrorName } from '../utils/isBultinErrorName';
|
||||||
|
|
||||||
|
import type { StackFrame as StackFrameType } from '../utils/stack-frame';
|
||||||
|
import type { ErrorLocation } from '../utils/parseCompileError';
|
||||||
|
|
||||||
|
const traceStyle = {
|
||||||
|
fontSize: '1em',
|
||||||
|
flex: '0 1 auto',
|
||||||
|
minHeight: '0px',
|
||||||
|
overflow: 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {|
|
||||||
|
stackFrames: StackFrameType[],
|
||||||
|
errorName: string,
|
||||||
|
contextSize: number,
|
||||||
|
editorHandler: (errorLoc: ErrorLocation) => void,
|
||||||
|
|};
|
||||||
|
|
||||||
|
class StackTrace extends Component<Props> {
|
||||||
|
renderFrames() {
|
||||||
|
const { stackFrames, errorName, contextSize, editorHandler } = this.props;
|
||||||
|
const renderedFrames = [];
|
||||||
|
let hasReachedAppCode = false,
|
||||||
|
currentBundle = [],
|
||||||
|
bundleCount = 0,
|
||||||
|
anyNodeExpanded = false;
|
||||||
|
|
||||||
|
stackFrames.forEach((frame, index) => {
|
||||||
|
const { fileName, _originalFileName: sourceFileName } = frame;
|
||||||
|
const isInternalUrl = isInternalFile(sourceFileName, fileName);
|
||||||
|
const isThrownIntentionally = !isBultinErrorName(errorName);
|
||||||
|
const shouldCollapse =
|
||||||
|
isInternalUrl && (isThrownIntentionally || hasReachedAppCode);
|
||||||
|
|
||||||
|
if (!shouldCollapse) {
|
||||||
|
anyNodeExpanded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInternalUrl) {
|
||||||
|
hasReachedAppCode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frameEle = (
|
||||||
|
<StackFrame
|
||||||
|
key={'frame-' + index}
|
||||||
|
frame={frame}
|
||||||
|
contextSize={contextSize}
|
||||||
|
critical={index === 0}
|
||||||
|
showCode={!shouldCollapse}
|
||||||
|
editorHandler={editorHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const lastElement = index === stackFrames.length - 1;
|
||||||
|
|
||||||
|
if (shouldCollapse) {
|
||||||
|
currentBundle.push(frameEle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldCollapse || lastElement) {
|
||||||
|
if (currentBundle.length === 1) {
|
||||||
|
renderedFrames.push(currentBundle[0]);
|
||||||
|
} else if (currentBundle.length > 1) {
|
||||||
|
bundleCount++;
|
||||||
|
renderedFrames.push(
|
||||||
|
<Collapsible collapsedByDefault={anyNodeExpanded} key={'bundle-' + bundleCount}>
|
||||||
|
{currentBundle}
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
currentBundle = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldCollapse) {
|
||||||
|
renderedFrames.push(frameEle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderedFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div style={traceStyle}>{this.renderFrames()}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackTrace;
|
42
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/absolutifyCaret.js
vendored
Normal file
42
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/absolutifyCaret.js
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function removeNextBr(parent, component: ?Element) {
|
||||||
|
while (component != null && component.tagName.toLowerCase() !== 'br') {
|
||||||
|
component = component.nextElementSibling;
|
||||||
|
}
|
||||||
|
if (component != null) {
|
||||||
|
parent.removeChild(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function absolutifyCaret(component: Node) {
|
||||||
|
const ccn = component.childNodes;
|
||||||
|
for (let index = 0; index < ccn.length; ++index) {
|
||||||
|
const c = ccn[index];
|
||||||
|
// $FlowFixMe
|
||||||
|
if (c.tagName.toLowerCase() !== 'span') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const _text = c.innerText;
|
||||||
|
if (_text == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const text = _text.replace(/\s/g, '');
|
||||||
|
if (text !== '|^') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// $FlowFixMe
|
||||||
|
c.style.position = 'absolute';
|
||||||
|
c.style.marginTop = '-7px';
|
||||||
|
// $FlowFixMe
|
||||||
|
removeNextBr(component, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { absolutifyCaret };
|
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/css.js
vendored
Normal file
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/css.js
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
let injectedCount = 0;
|
||||||
|
const injectedCache = {};
|
||||||
|
|
||||||
|
function getHead(document: Document) {
|
||||||
|
return document.head || document.getElementsByTagName('head')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectCss(document: Document, css: string): number {
|
||||||
|
const head = getHead(document);
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.type = 'text/css';
|
||||||
|
style.appendChild(document.createTextNode(css));
|
||||||
|
head.appendChild(style);
|
||||||
|
|
||||||
|
injectedCache[++injectedCount] = style;
|
||||||
|
return injectedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCss(document: Document, ref: number) {
|
||||||
|
if (injectedCache[ref] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const head = getHead(document);
|
||||||
|
head.removeChild(injectedCache[ref]);
|
||||||
|
delete injectedCache[ref];
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStyles(element: HTMLElement, styles: Object) {
|
||||||
|
element.setAttribute('style', '');
|
||||||
|
for (const key in styles) {
|
||||||
|
if (!styles.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// $FlowFixMe
|
||||||
|
element.style[key] = styles[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getHead, injectCss, removeCss, applyStyles };
|
73
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/generateAnsiHTML.js
vendored
Normal file
73
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/generateAnsiHTML.js
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import Anser from 'anser';
|
||||||
|
import { nicinabox as theme } from 'redux-devtools-themes';
|
||||||
|
import { AllHtmlEntities as Entities } from 'html-entities';
|
||||||
|
|
||||||
|
var entities = new Entities();
|
||||||
|
|
||||||
|
var anserMap = {
|
||||||
|
'ansi-bright-black': theme.base03,
|
||||||
|
'ansi-bright-yellow': theme.base0A,
|
||||||
|
'ansi-yellow': theme.base0B,
|
||||||
|
'ansi-bright-green': theme.base0B,
|
||||||
|
'ansi-green': theme.base0F,
|
||||||
|
'ansi-bright-cyan': theme.base0D,
|
||||||
|
'ansi-cyan': theme.base0C,
|
||||||
|
'ansi-bright-red': theme.base09,
|
||||||
|
'ansi-red': theme.base0E,
|
||||||
|
'ansi-bright-magenta': theme.base0F,
|
||||||
|
'ansi-magenta': theme.base0E,
|
||||||
|
'ansi-white': theme.base00,
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateAnsiHTML(txt: string): string {
|
||||||
|
var arr = new Anser().ansiToJson(entities.encode(txt), {
|
||||||
|
use_classes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = '';
|
||||||
|
var open = false;
|
||||||
|
for (var index = 0; index < arr.length; ++index) {
|
||||||
|
var c = arr[index];
|
||||||
|
var content = c.content,
|
||||||
|
fg = c.fg;
|
||||||
|
|
||||||
|
var contentParts = content.split('\n');
|
||||||
|
for (var _index = 0; _index < contentParts.length; ++_index) {
|
||||||
|
if (!open) {
|
||||||
|
result += '<span data-ansi-line="true">';
|
||||||
|
open = true;
|
||||||
|
}
|
||||||
|
var part = contentParts[_index].replace('\r', '');
|
||||||
|
var color = anserMap[fg];
|
||||||
|
if (color != null) {
|
||||||
|
result += '<span style="color: ' + color + ';">' + part + '</span>';
|
||||||
|
} else {
|
||||||
|
if (fg != null) {
|
||||||
|
console.log('Missing color mapping:', fg); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
result += '<span>' + part + '</span>';
|
||||||
|
}
|
||||||
|
if (_index < contentParts.length - 1) {
|
||||||
|
result += '</span>';
|
||||||
|
open = false;
|
||||||
|
result += '<br/>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (open) {
|
||||||
|
result += '</span>';
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default generateAnsiHTML;
|
37
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getLinesAround.js
vendored
Normal file
37
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getLinesAround.js
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import { ScriptLine } from './stack-frame';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} line The line number to provide context around.
|
||||||
|
* @param {number} count The number of lines you'd like for context.
|
||||||
|
* @param {string[] | string} lines The source code.
|
||||||
|
*/
|
||||||
|
function getLinesAround(
|
||||||
|
line: number,
|
||||||
|
count: number,
|
||||||
|
lines: string[] | string
|
||||||
|
): ScriptLine[] {
|
||||||
|
if (typeof lines === 'string') {
|
||||||
|
lines = lines.split('\n');
|
||||||
|
}
|
||||||
|
const result = [];
|
||||||
|
for (
|
||||||
|
let index = Math.max(0, line - 1 - count);
|
||||||
|
index <= Math.min(lines.length - 1, line - 1 + count);
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
result.push(new ScriptLine(index + 1, lines[index], index === line - 1));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getLinesAround };
|
||||||
|
export default getLinesAround;
|
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getPrettyURL.js
vendored
Normal file
47
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getPrettyURL.js
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function getPrettyURL(
|
||||||
|
sourceFileName: ?string,
|
||||||
|
sourceLineNumber: ?number,
|
||||||
|
sourceColumnNumber: ?number,
|
||||||
|
fileName: ?string,
|
||||||
|
lineNumber: ?number,
|
||||||
|
columnNumber: ?number,
|
||||||
|
compiled: boolean
|
||||||
|
): string {
|
||||||
|
let prettyURL;
|
||||||
|
if (!compiled && sourceFileName && typeof sourceLineNumber === 'number') {
|
||||||
|
// Remove everything up to the first /src/ or /node_modules/
|
||||||
|
const trimMatch = /^[/|\\].*?[/|\\]((src|node_modules)[/|\\].*)/.exec(
|
||||||
|
sourceFileName
|
||||||
|
);
|
||||||
|
if (trimMatch && trimMatch[1]) {
|
||||||
|
prettyURL = trimMatch[1];
|
||||||
|
} else {
|
||||||
|
prettyURL = sourceFileName;
|
||||||
|
}
|
||||||
|
prettyURL += ':' + sourceLineNumber;
|
||||||
|
// Note: we intentionally skip 0's because they're produced by cheap Webpack maps
|
||||||
|
if (sourceColumnNumber) {
|
||||||
|
prettyURL += ':' + sourceColumnNumber;
|
||||||
|
}
|
||||||
|
} else if (fileName && typeof lineNumber === 'number') {
|
||||||
|
prettyURL = fileName + ':' + lineNumber;
|
||||||
|
// Note: we intentionally skip 0's because they're produced by cheap Webpack maps
|
||||||
|
if (columnNumber) {
|
||||||
|
prettyURL += ':' + columnNumber;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prettyURL = 'unknown';
|
||||||
|
}
|
||||||
|
return prettyURL.replace('webpack://', '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getPrettyURL };
|
||||||
|
export default getPrettyURL;
|
160
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getSourceMap.js
vendored
Normal file
160
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getSourceMap.js
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import { SourceMapConsumer } from 'source-map';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapped instance of a <code>{@link https://github.com/mozilla/source-map SourceMapConsumer}</code>.
|
||||||
|
*
|
||||||
|
* This exposes methods which will be indifferent to changes made in <code>{@link https://github.com/mozilla/source-map source-map}</code>.
|
||||||
|
*/
|
||||||
|
class SourceMap {
|
||||||
|
__source_map: SourceMapConsumer;
|
||||||
|
|
||||||
|
constructor(sourceMap) {
|
||||||
|
this.__source_map = sourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original code position for a generated code position.
|
||||||
|
* @param {number} line The line of the generated code position.
|
||||||
|
* @param {number} column The column of the generated code position.
|
||||||
|
*/
|
||||||
|
getOriginalPosition(
|
||||||
|
line: number,
|
||||||
|
column: number
|
||||||
|
): { source: string, line: number, column: number } {
|
||||||
|
const {
|
||||||
|
line: l,
|
||||||
|
column: c,
|
||||||
|
source: s,
|
||||||
|
} = this.__source_map.originalPositionFor({
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
});
|
||||||
|
return { line: l, column: c, source: s };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the generated code position for an original position.
|
||||||
|
* @param {string} source The source file of the original code position.
|
||||||
|
* @param {number} line The line of the original code position.
|
||||||
|
* @param {number} column The column of the original code position.
|
||||||
|
*/
|
||||||
|
getGeneratedPosition(
|
||||||
|
source: string,
|
||||||
|
line: number,
|
||||||
|
column: number
|
||||||
|
): { line: number, column: number } {
|
||||||
|
const { line: l, column: c } = this.__source_map.generatedPositionFor({
|
||||||
|
source,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
line: l,
|
||||||
|
column: c,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the code for a given source file name.
|
||||||
|
* @param {string} sourceName The name of the source file.
|
||||||
|
*/
|
||||||
|
getSource(sourceName: string): string {
|
||||||
|
return this.__source_map.sourceContentFor(sourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSources(): string[] {
|
||||||
|
return this.__source_map.sources;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractSourceMapUrl(
|
||||||
|
fileUri: string,
|
||||||
|
fileContents: string
|
||||||
|
): Promise<string> {
|
||||||
|
const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm;
|
||||||
|
let match = null;
|
||||||
|
for (;;) {
|
||||||
|
let next = regex.exec(fileContents);
|
||||||
|
if (next == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match = next;
|
||||||
|
}
|
||||||
|
if (!(match && match[1])) {
|
||||||
|
return Promise.reject(`Cannot find a source map directive for ${fileUri}.`);
|
||||||
|
}
|
||||||
|
return Promise.resolve(match[1].toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of <code>{@link SourceMap}</code> for a given fileUri and fileContents.
|
||||||
|
* @param {string} fileUri The URI of the source file.
|
||||||
|
* @param {string} fileContents The contents of the source file.
|
||||||
|
*/
|
||||||
|
async function getSourceMap(
|
||||||
|
//function getSourceMap(
|
||||||
|
fileUri: string,
|
||||||
|
fileContents: string
|
||||||
|
): Promise<SourceMap> {
|
||||||
|
|
||||||
|
let sm = await extractSourceMapUrl(fileUri, fileContents);
|
||||||
|
if (sm.indexOf('data:') === 0) {
|
||||||
|
const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/;
|
||||||
|
const match2 = sm.match(base64);
|
||||||
|
if (!match2) {
|
||||||
|
throw new Error(
|
||||||
|
'Sorry, non-base64 inline source-map encoding is not supported.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sm = sm.substring(match2[0].length);
|
||||||
|
sm = window.atob(sm);
|
||||||
|
sm = JSON.parse(sm);
|
||||||
|
return new SourceMap(new SourceMapConsumer(sm));
|
||||||
|
} else {
|
||||||
|
const index = fileUri.lastIndexOf('/');
|
||||||
|
const url = fileUri.substring(0, index + 1) + sm;
|
||||||
|
const obj = await fetch(url).then(res => res.json());
|
||||||
|
return new SourceMap(new SourceMapConsumer(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
return extractSourceMapUrl(fileUri, fileContents)
|
||||||
|
.then(sm => {
|
||||||
|
if (sm.indexOf('data:') === 0) {
|
||||||
|
const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/;
|
||||||
|
const match2 = sm.match(base64);
|
||||||
|
if (!match2) {
|
||||||
|
throw new Error(
|
||||||
|
'Sorry, non-base64 inline source-map encoding is not supported.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sm = sm.substring(match2[0].length);
|
||||||
|
sm = window.atob(sm);
|
||||||
|
sm = JSON.parse(sm);
|
||||||
|
return new SourceMap(new SourceMapConsumer(sm));
|
||||||
|
} else {
|
||||||
|
const index = fileUri.lastIndexOf('/');
|
||||||
|
const url = fileUri.substring(0, index + 1) + sm;
|
||||||
|
|
||||||
|
return fetch(url).then(res => res.json())
|
||||||
|
.then(obj => {
|
||||||
|
return new SourceMap(new SourceMapConsumer(obj))
|
||||||
|
})
|
||||||
|
//const obj = await fetch(url).then(res => res.json());
|
||||||
|
//return new SourceMap(new SourceMapConsumer(obj));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export { extractSourceMapUrl, getSourceMap };
|
||||||
|
export default getSourceMap;
|
52
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getStackFrames.js
vendored
Normal file
52
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getStackFrames.js
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import type { StackFrame } from './stack-frame';
|
||||||
|
import { parse } from './parser';
|
||||||
|
import { map } from './mapper';
|
||||||
|
import { unmap } from './unmapper';
|
||||||
|
import { toExclude } from '../../presets';
|
||||||
|
|
||||||
|
function getStackFrames(
|
||||||
|
error: Error,
|
||||||
|
unhandledRejection: boolean = false,
|
||||||
|
contextSize: number = 3
|
||||||
|
): Promise<StackFrame[] | null> {
|
||||||
|
const parsedFrames = parse(error);
|
||||||
|
let enhancedFramesPromise;
|
||||||
|
if (error.__unmap_source) {
|
||||||
|
enhancedFramesPromise = unmap(
|
||||||
|
// $FlowFixMe
|
||||||
|
error.__unmap_source,
|
||||||
|
parsedFrames,
|
||||||
|
contextSize
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return enhancedFrames.filter(
|
||||||
|
({ functionName, fileName }) =>
|
||||||
|
(functionName == null ||
|
||||||
|
functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1) &&
|
||||||
|
!toExclude.test(fileName)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getStackFrames;
|
||||||
|
export { getStackFrames };
|
25
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isBultinErrorName.js
vendored
Normal file
25
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isBultinErrorName.js
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function isBultinErrorName(errorName: ?string) {
|
||||||
|
switch (errorName) {
|
||||||
|
case 'EvalError':
|
||||||
|
case 'InternalError':
|
||||||
|
case 'RangeError':
|
||||||
|
case 'ReferenceError':
|
||||||
|
case 'SyntaxError':
|
||||||
|
case 'TypeError':
|
||||||
|
case 'URIError':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isBultinErrorName };
|
||||||
|
export default isBultinErrorName;
|
21
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isInternalFile.js
vendored
Normal file
21
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/isInternalFile.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
function isInternalFile(sourceFileName: ?string, fileName: ?string) {
|
||||||
|
return (
|
||||||
|
sourceFileName == null ||
|
||||||
|
sourceFileName === '' ||
|
||||||
|
sourceFileName.indexOf('/~/') !== -1 ||
|
||||||
|
sourceFileName.indexOf('/node_modules/') !== -1 ||
|
||||||
|
sourceFileName.trim().indexOf(' ') !== -1 ||
|
||||||
|
fileName == null ||
|
||||||
|
fileName === ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isInternalFile };
|
69
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/mapper.js
vendored
Normal file
69
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/mapper.js
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import StackFrame from './stack-frame';
|
||||||
|
import { getSourceMap } from './getSourceMap';
|
||||||
|
import { getLinesAround } from './getLinesAround';
|
||||||
|
import { settle } from 'settle-promise';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhances a set of <code>StackFrame</code>s with their original positions and code (when available).
|
||||||
|
* @param {StackFrame[]} frames A set of <code>StackFrame</code>s which contain (generated) code positions.
|
||||||
|
* @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the <code>StackFrame</code>.
|
||||||
|
*/
|
||||||
|
async function map(
|
||||||
|
frames: StackFrame[],
|
||||||
|
contextLines: number = 3
|
||||||
|
): Promise<StackFrame[]> {
|
||||||
|
const cache: any = {};
|
||||||
|
const files: string[] = [];
|
||||||
|
frames.forEach(frame => {
|
||||||
|
const { fileName } = frame;
|
||||||
|
if (fileName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (files.indexOf(fileName) !== -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
files.push(fileName);
|
||||||
|
});
|
||||||
|
await settle(
|
||||||
|
files.map(async fileName => {
|
||||||
|
const fileSource = await fetch(fileName).then(r => r.text());
|
||||||
|
const map = await getSourceMap(fileName, fileSource);
|
||||||
|
cache[fileName] = { fileSource, map };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return frames.map(frame => {
|
||||||
|
const { functionName, fileName, lineNumber, columnNumber } = frame;
|
||||||
|
let { map, fileSource } = cache[fileName] || {};
|
||||||
|
if (map == null || lineNumber == null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
const { source, line, column } = map.getOriginalPosition(
|
||||||
|
lineNumber,
|
||||||
|
columnNumber
|
||||||
|
);
|
||||||
|
const originalSource = source == null ? [] : map.getSource(source) || [];
|
||||||
|
return new StackFrame(
|
||||||
|
functionName,
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
getLinesAround(lineNumber, contextLines, fileSource),
|
||||||
|
functionName,
|
||||||
|
source,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
getLinesAround(line, contextLines, originalSource)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { map };
|
||||||
|
export default map;
|
61
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parseCompileError.js
vendored
Normal file
61
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parseCompileError.js
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
import Anser from 'anser';
|
||||||
|
|
||||||
|
export type ErrorLocation = {|
|
||||||
|
fileName: string,
|
||||||
|
lineNumber: number,
|
||||||
|
colNumber?: number,
|
||||||
|
|};
|
||||||
|
|
||||||
|
const filePathRegex = /^\.(\/[^/\n ]+)+\.[^/\n ]+$/;
|
||||||
|
|
||||||
|
const lineNumberRegexes = [
|
||||||
|
// Babel syntax errors
|
||||||
|
// Based on syntax error formating of babylon parser
|
||||||
|
// https://github.com/babel/babylon/blob/v7.0.0-beta.22/src/parser/location.js#L19
|
||||||
|
/^.*\((\d+):(\d+)\)$/,
|
||||||
|
|
||||||
|
// ESLint errors
|
||||||
|
// Based on eslintFormatter in react-dev-utils
|
||||||
|
/^Line (\d+):.+$/,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Based on error formatting of webpack
|
||||||
|
// https://github.com/webpack/webpack/blob/v3.5.5/lib/Stats.js#L183-L217
|
||||||
|
function parseCompileError(message: string): ?ErrorLocation {
|
||||||
|
const lines: Array<string> = message.split('\n');
|
||||||
|
let fileName: string = '';
|
||||||
|
let lineNumber: number = 0;
|
||||||
|
let colNumber: number = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line: string = Anser.ansiToText(lines[i]).trim();
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileName && line.match(filePathRegex)) {
|
||||||
|
fileName = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = 0;
|
||||||
|
while (k < lineNumberRegexes.length) {
|
||||||
|
const match: ?Array<string> = line.match(lineNumberRegexes[k]);
|
||||||
|
if (match) {
|
||||||
|
lineNumber = parseInt(match[1], 10);
|
||||||
|
// colNumber starts with 0 and hence add 1
|
||||||
|
colNumber = parseInt(match[2], 10) + 1 || 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName && lineNumber) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName && lineNumber ? { fileName, lineNumber, colNumber } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseCompileError;
|
91
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parser.js
vendored
Normal file
91
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/parser.js
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import StackFrame from './stack-frame';
|
||||||
|
|
||||||
|
const regexExtractLocation = /\(?(.+?)(?::(\d+))?(?::(\d+))?\)?$/;
|
||||||
|
|
||||||
|
function extractLocation(token: string): [string, number, number] {
|
||||||
|
return regexExtractLocation
|
||||||
|
.exec(token)
|
||||||
|
.slice(1)
|
||||||
|
.map(v => {
|
||||||
|
const p = Number(v);
|
||||||
|
if (!isNaN(p)) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const regexValidFrame_Chrome = /^\s*(at|in)\s.+(:\d+)/;
|
||||||
|
const regexValidFrame_FireFox = /(^|@)\S+:\d+|.+line\s+\d+\s+>\s+(eval|Function).+/;
|
||||||
|
|
||||||
|
function parseStack(stack: string[]): StackFrame[] {
|
||||||
|
const frames = stack
|
||||||
|
.filter(
|
||||||
|
e => regexValidFrame_Chrome.test(e) || regexValidFrame_FireFox.test(e)
|
||||||
|
)
|
||||||
|
.map(e => {
|
||||||
|
if (regexValidFrame_FireFox.test(e)) {
|
||||||
|
// Strip eval, we don't care about it
|
||||||
|
let isEval = false;
|
||||||
|
if (/ > (eval|Function)/.test(e)) {
|
||||||
|
e = e.replace(
|
||||||
|
/ line (\d+)(?: > eval line \d+)* > (eval|Function):\d+:\d+/g,
|
||||||
|
':$1'
|
||||||
|
);
|
||||||
|
isEval = true;
|
||||||
|
}
|
||||||
|
const data = e.split(/[@]/g);
|
||||||
|
const last = data.pop();
|
||||||
|
return new StackFrame(
|
||||||
|
data.join('@') || (isEval ? 'eval' : null),
|
||||||
|
...extractLocation(last)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Strip eval, we don't care about it
|
||||||
|
if (e.indexOf('(eval ') !== -1) {
|
||||||
|
e = e.replace(/(\(eval at [^()]*)|(\),.*$)/g, '');
|
||||||
|
}
|
||||||
|
if (e.indexOf('(at ') !== -1) {
|
||||||
|
e = e.replace(/\(at /, '(');
|
||||||
|
}
|
||||||
|
const data = e
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/g)
|
||||||
|
.slice(1);
|
||||||
|
const last = data.pop();
|
||||||
|
return new StackFrame(data.join(' ') || null, ...extractLocation(last));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns an <code>Error</code>, or similar object, into a set of <code>StackFrame</code>s.
|
||||||
|
* @alias parse
|
||||||
|
*/
|
||||||
|
function parseError(error: Error | string | string[]): StackFrame[] {
|
||||||
|
if (error == null) {
|
||||||
|
throw new Error('You cannot pass a null object.');
|
||||||
|
}
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
return parseStack(error.split('\n'));
|
||||||
|
}
|
||||||
|
if (Array.isArray(error)) {
|
||||||
|
return parseStack(error);
|
||||||
|
}
|
||||||
|
if (typeof error.stack === 'string') {
|
||||||
|
return parseStack(error.stack.split('\n'));
|
||||||
|
}
|
||||||
|
throw new Error('The error you provided does not contain a stack trace.');
|
||||||
|
}
|
||||||
|
|
||||||
|
export { parseError as parse };
|
||||||
|
export default parseError;
|
18
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/pollyfills.js
vendored
Normal file
18
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/pollyfills.js
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (typeof Promise === 'undefined') {
|
||||||
|
// Rejection tracking prevents a common issue where React gets into an
|
||||||
|
// inconsistent state due to an error, but it gets swallowed by a Promise,
|
||||||
|
// and the user has no idea what causes React's erratic future behavior.
|
||||||
|
require('promise/lib/rejection-tracking').enable();
|
||||||
|
window.Promise = require('promise/lib/es6-extensions.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.assign() is commonly used with React.
|
||||||
|
// It will use the native implementation if it's present and isn't buggy.
|
||||||
|
Object.assign = require('object-assign');
|
120
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/stack-frame.js
vendored
Normal file
120
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/stack-frame.js
vendored
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
/** A container holding a script line. */
|
||||||
|
class ScriptLine {
|
||||||
|
/** The line number of this line of source. */
|
||||||
|
lineNumber: number;
|
||||||
|
/** The content (or value) of this line of source. */
|
||||||
|
content: string;
|
||||||
|
/** Whether or not this line should be highlighted. Particularly useful for error reporting with context. */
|
||||||
|
highlight: boolean;
|
||||||
|
|
||||||
|
constructor(lineNumber: number, content: string, highlight: boolean = false) {
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.content = content;
|
||||||
|
this.highlight = highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a stack frame.
|
||||||
|
*/
|
||||||
|
class StackFrame {
|
||||||
|
functionName: string | null;
|
||||||
|
fileName: string | null;
|
||||||
|
lineNumber: number | null;
|
||||||
|
columnNumber: number | null;
|
||||||
|
|
||||||
|
_originalFunctionName: string | null;
|
||||||
|
_originalFileName: string | null;
|
||||||
|
_originalLineNumber: number | null;
|
||||||
|
_originalColumnNumber: number | null;
|
||||||
|
|
||||||
|
_scriptCode: ScriptLine[] | null;
|
||||||
|
_originalScriptCode: ScriptLine[] | null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
functionName: string | null = null,
|
||||||
|
fileName: string | null = null,
|
||||||
|
lineNumber: number | null = null,
|
||||||
|
columnNumber: number | null = null,
|
||||||
|
scriptCode: ScriptLine[] | null = null,
|
||||||
|
sourceFunctionName: string | null = null,
|
||||||
|
sourceFileName: string | null = null,
|
||||||
|
sourceLineNumber: number | null = null,
|
||||||
|
sourceColumnNumber: number | null = null,
|
||||||
|
sourceScriptCode: ScriptLine[] | null = null
|
||||||
|
) {
|
||||||
|
if (functionName && functionName.indexOf('Object.') === 0) {
|
||||||
|
functionName = functionName.slice('Object.'.length);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
// Chrome has a bug with inferring function.name:
|
||||||
|
// https://github.com/facebook/create-react-app/issues/2097
|
||||||
|
// Let's ignore a meaningless name we get for top-level modules.
|
||||||
|
functionName === 'friendlySyntaxErrorLabel' ||
|
||||||
|
functionName === 'exports.__esModule' ||
|
||||||
|
functionName === '<anonymous>' ||
|
||||||
|
!functionName
|
||||||
|
) {
|
||||||
|
functionName = null;
|
||||||
|
}
|
||||||
|
this.functionName = functionName;
|
||||||
|
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.columnNumber = columnNumber;
|
||||||
|
|
||||||
|
this._originalFunctionName = sourceFunctionName;
|
||||||
|
this._originalFileName = sourceFileName;
|
||||||
|
this._originalLineNumber = sourceLineNumber;
|
||||||
|
this._originalColumnNumber = sourceColumnNumber;
|
||||||
|
|
||||||
|
this._scriptCode = scriptCode;
|
||||||
|
this._originalScriptCode = sourceScriptCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this function.
|
||||||
|
*/
|
||||||
|
getFunctionName(): string {
|
||||||
|
return this.functionName || '(anonymous function)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source of the frame.
|
||||||
|
* This contains the file name, line number, and column number when available.
|
||||||
|
*/
|
||||||
|
getSource(): string {
|
||||||
|
let str = '';
|
||||||
|
if (this.fileName != null) {
|
||||||
|
str += this.fileName + ':';
|
||||||
|
}
|
||||||
|
if (this.lineNumber != null) {
|
||||||
|
str += this.lineNumber + ':';
|
||||||
|
}
|
||||||
|
if (this.columnNumber != null) {
|
||||||
|
str += this.columnNumber + ':';
|
||||||
|
}
|
||||||
|
return str.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pretty version of this stack frame.
|
||||||
|
*/
|
||||||
|
toString(): string {
|
||||||
|
const functionName = this.getFunctionName();
|
||||||
|
const source = this.getSource();
|
||||||
|
return `${functionName}${source ? ` (${source})` : ''}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { StackFrame, ScriptLine };
|
||||||
|
export default StackFrame;
|
126
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/unmapper.js
vendored
Normal file
126
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/unmapper.js
vendored
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
import StackFrame from './stack-frame';
|
||||||
|
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);
|
||||||
|
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.
|
||||||
|
* @param {StackFrame[]} frames A set of <code>StackFrame</code>s which are already mapped and missing their generated positions.
|
||||||
|
* @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the <code>StackFrame</code>.
|
||||||
|
*/
|
||||||
|
async function unmap(
|
||||||
|
_fileUri: string | { uri: string, contents: string },
|
||||||
|
frames: StackFrame[],
|
||||||
|
contextLines: number = 3
|
||||||
|
): Promise<StackFrame[]> {
|
||||||
|
let fileContents = typeof _fileUri === 'object' ? _fileUri.contents : null;
|
||||||
|
let fileUri = typeof _fileUri === 'object' ? _fileUri.uri : _fileUri;
|
||||||
|
if (fileContents == null) {
|
||||||
|
fileContents = await fetch(fileUri).then(res => res.text());
|
||||||
|
}
|
||||||
|
const map = await getSourceMap(fileUri, fileContents);
|
||||||
|
return frames.map(frame => {
|
||||||
|
const {
|
||||||
|
functionName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
_originalLineNumber,
|
||||||
|
} = frame;
|
||||||
|
if (_originalLineNumber != null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
let { fileName } = frame;
|
||||||
|
if (fileName) {
|
||||||
|
// The web version of this module only provides POSIX support, so Windows
|
||||||
|
// paths like C:\foo\\baz\..\\bar\ cannot be normalized.
|
||||||
|
// A simple solution to this is to replace all `\` with `/`, then
|
||||||
|
// normalize afterwards.
|
||||||
|
fileName = path.normalize(fileName.replace(/[\\]+/g, '/'));
|
||||||
|
}
|
||||||
|
if (fileName == null) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
const fN: string = fileName;
|
||||||
|
const source = map
|
||||||
|
.getSources()
|
||||||
|
// Prepare path for normalization; see comment above for reasoning.
|
||||||
|
.map(s => s.replace(/[\\]+/g, '/'))
|
||||||
|
.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) => {
|
||||||
|
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(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
functionName,
|
||||||
|
fN,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const sourceT = source[0].token;
|
||||||
|
const { line, column } = map.getGeneratedPosition(
|
||||||
|
sourceT,
|
||||||
|
lineNumber,
|
||||||
|
// $FlowFixMe
|
||||||
|
columnNumber
|
||||||
|
);
|
||||||
|
const originalSource = map.getSource(sourceT);
|
||||||
|
return new StackFrame(
|
||||||
|
functionName,
|
||||||
|
fileUri,
|
||||||
|
line,
|
||||||
|
column || null,
|
||||||
|
getLinesAround(line, contextLines, fileContents || []),
|
||||||
|
functionName,
|
||||||
|
fN,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
getLinesAround(lineNumber, contextLines, originalSource)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { unmap };
|
||||||
|
export default unmap;
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { configure, mount } from 'enzyme';
|
||||||
|
import toJson from 'enzyme-to-json';
|
||||||
|
import StackTraceTab from '../src/StackTraceTab';
|
||||||
|
|
||||||
|
import Adapter from 'enzyme-adapter-react-15';
|
||||||
|
configure({ adapter: new Adapter() });
|
||||||
|
|
||||||
|
function genAsyncSnapshot(component, done) {
|
||||||
|
setTimeout(() => {
|
||||||
|
component.update();
|
||||||
|
expect(toJson(component)).toMatchSnapshot();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
0: { type: 'PERFORM_ACTION', action: { type: '@@INIT' } },
|
||||||
|
1: { type: 'PERFORM_ACTION', action: { type: 'INCREMENT_COUNTER' } },
|
||||||
|
2: {
|
||||||
|
type: 'PERFORM_ACTION', action: { type: 'INCREMENT_COUNTER' },
|
||||||
|
stack: 'Error\n at fn1 (app.js:72:24)\n at fn2 (app.js:84:31)\n at fn3 (chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/page.bundle.js:1269:80)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('StackTraceTab component', () => {
|
||||||
|
it('should render with no props', (done) => {
|
||||||
|
const component = mount(<StackTraceTab />);
|
||||||
|
genAsyncSnapshot(component, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with props, but without stack', (done) => {
|
||||||
|
const component = mount(
|
||||||
|
<StackTraceTab
|
||||||
|
actions={actions} action={actions[0].action}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
genAsyncSnapshot(component, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render the link to docs', (done) => {
|
||||||
|
const component = mount(
|
||||||
|
<StackTraceTab
|
||||||
|
actions={actions} action={actions[1].action}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
genAsyncSnapshot(component, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with trace stack', (done) => {
|
||||||
|
const component = mount(
|
||||||
|
<StackTraceTab
|
||||||
|
actions={actions} action={actions[2].action}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
genAsyncSnapshot(component, done);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,379 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`StackTraceTab component should render the link to docs 1`] = `
|
||||||
|
<StackTraceTab
|
||||||
|
action={
|
||||||
|
Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
Object {
|
||||||
|
"0": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "@@INIT",
|
||||||
|
},
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
"1": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
},
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
"2": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
},
|
||||||
|
"stack": "Error
|
||||||
|
at fn1 (app.js:72:24)
|
||||||
|
at fn2 (app.js:84:31)
|
||||||
|
at fn3 (chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/page.bundle.js:1269:80)",
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": "5px 10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
To enable tracing action calls, you should set \`trace\` option to \`true\` for Redux DevTools enhancer. Refer to
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
this page
|
||||||
|
</a>
|
||||||
|
for more details.
|
||||||
|
</div>
|
||||||
|
</StackTraceTab>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`StackTraceTab component should render with no props 1`] = `
|
||||||
|
<StackTraceTab>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": "5px 10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StackTrace
|
||||||
|
contextSize={3}
|
||||||
|
editorHandler={[Function]}
|
||||||
|
errorName="N/A"
|
||||||
|
stackFrames={Array []}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"flex": "0 1 auto",
|
||||||
|
"fontSize": "1em",
|
||||||
|
"minHeight": "0px",
|
||||||
|
"overflow": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackTrace>
|
||||||
|
</div>
|
||||||
|
</StackTraceTab>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`StackTraceTab component should render with props, but without stack 1`] = `
|
||||||
|
<StackTraceTab
|
||||||
|
action={
|
||||||
|
Object {
|
||||||
|
"type": "@@INIT",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
Object {
|
||||||
|
"0": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "@@INIT",
|
||||||
|
},
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
"1": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
},
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
"2": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
},
|
||||||
|
"stack": "Error
|
||||||
|
at fn1 (app.js:72:24)
|
||||||
|
at fn2 (app.js:84:31)
|
||||||
|
at fn3 (chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/page.bundle.js:1269:80)",
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": "5px 10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StackTrace
|
||||||
|
contextSize={3}
|
||||||
|
editorHandler={[Function]}
|
||||||
|
errorName="N/A"
|
||||||
|
stackFrames={Array []}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"flex": "0 1 auto",
|
||||||
|
"fontSize": "1em",
|
||||||
|
"minHeight": "0px",
|
||||||
|
"overflow": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StackTrace>
|
||||||
|
</div>
|
||||||
|
</StackTraceTab>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`StackTraceTab component should render with trace stack 1`] = `
|
||||||
|
<StackTraceTab
|
||||||
|
action={
|
||||||
|
Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions={
|
||||||
|
Object {
|
||||||
|
"0": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "@@INIT",
|
||||||
|
},
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
"1": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
},
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
"2": Object {
|
||||||
|
"action": Object {
|
||||||
|
"type": "INCREMENT_COUNTER",
|
||||||
|
},
|
||||||
|
"stack": "Error
|
||||||
|
at fn1 (app.js:72:24)
|
||||||
|
at fn2 (app.js:84:31)
|
||||||
|
at fn3 (chrome-extension://lmhkpmbekcpmknklioeibfkpmmfibljd/js/page.bundle.js:1269:80)",
|
||||||
|
"type": "PERFORM_ACTION",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": "5px 10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StackTrace
|
||||||
|
contextSize={3}
|
||||||
|
editorHandler={[Function]}
|
||||||
|
errorName="N/A"
|
||||||
|
stackFrames={
|
||||||
|
Array [
|
||||||
|
StackFrame {
|
||||||
|
"_originalColumnNumber": null,
|
||||||
|
"_originalFileName": null,
|
||||||
|
"_originalFunctionName": null,
|
||||||
|
"_originalLineNumber": null,
|
||||||
|
"_originalScriptCode": null,
|
||||||
|
"_scriptCode": null,
|
||||||
|
"columnNumber": 24,
|
||||||
|
"fileName": "app.js",
|
||||||
|
"functionName": "fn1",
|
||||||
|
"lineNumber": 72,
|
||||||
|
},
|
||||||
|
StackFrame {
|
||||||
|
"_originalColumnNumber": null,
|
||||||
|
"_originalFileName": null,
|
||||||
|
"_originalFunctionName": null,
|
||||||
|
"_originalLineNumber": null,
|
||||||
|
"_originalScriptCode": null,
|
||||||
|
"_scriptCode": null,
|
||||||
|
"columnNumber": 31,
|
||||||
|
"fileName": "app.js",
|
||||||
|
"functionName": "fn2",
|
||||||
|
"lineNumber": 84,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"flex": "0 1 auto",
|
||||||
|
"fontSize": "1em",
|
||||||
|
"minHeight": "0px",
|
||||||
|
"overflow": "auto",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Collapsible
|
||||||
|
collapsedByDefault={false}
|
||||||
|
key="bundle-1"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#3C444F",
|
||||||
|
"border": "none",
|
||||||
|
"color": "#FFFFFF",
|
||||||
|
"cursor": "pointer",
|
||||||
|
"display": "block",
|
||||||
|
"fontSize": "1em",
|
||||||
|
"lineHeight": "1.5",
|
||||||
|
"marginBottom": "0.6em",
|
||||||
|
"padding": "0px 5px",
|
||||||
|
"textAlign": "left",
|
||||||
|
"width": "100%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
▼ 2 stack frames were expanded.
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"display": "block",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StackFrame
|
||||||
|
contextSize={3}
|
||||||
|
critical={true}
|
||||||
|
editorHandler={[Function]}
|
||||||
|
frame={
|
||||||
|
StackFrame {
|
||||||
|
"_originalColumnNumber": null,
|
||||||
|
"_originalFileName": null,
|
||||||
|
"_originalFunctionName": null,
|
||||||
|
"_originalLineNumber": null,
|
||||||
|
"_originalScriptCode": null,
|
||||||
|
"_scriptCode": null,
|
||||||
|
"columnNumber": 24,
|
||||||
|
"fileName": "app.js",
|
||||||
|
"functionName": "fn1",
|
||||||
|
"lineNumber": 72,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key="frame-0"
|
||||||
|
showCode={false}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
fn1
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "0.9em",
|
||||||
|
"marginBottom": "0.9em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
onClick={null}
|
||||||
|
onKeyDown={null}
|
||||||
|
style={null}
|
||||||
|
tabIndex={null}
|
||||||
|
>
|
||||||
|
app.js:72:24
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StackFrame>
|
||||||
|
<StackFrame
|
||||||
|
contextSize={3}
|
||||||
|
critical={false}
|
||||||
|
editorHandler={[Function]}
|
||||||
|
frame={
|
||||||
|
StackFrame {
|
||||||
|
"_originalColumnNumber": null,
|
||||||
|
"_originalFileName": null,
|
||||||
|
"_originalFunctionName": null,
|
||||||
|
"_originalLineNumber": null,
|
||||||
|
"_originalScriptCode": null,
|
||||||
|
"_scriptCode": null,
|
||||||
|
"columnNumber": 31,
|
||||||
|
"fileName": "app.js",
|
||||||
|
"functionName": "fn2",
|
||||||
|
"lineNumber": 84,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key="frame-1"
|
||||||
|
showCode={false}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
fn2
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "0.9em",
|
||||||
|
"marginBottom": "0.9em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
onClick={null}
|
||||||
|
onKeyDown={null}
|
||||||
|
style={null}
|
||||||
|
tabIndex={null}
|
||||||
|
>
|
||||||
|
app.js:84:31
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</StackFrame>
|
||||||
|
<button
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundColor": "#3C444F",
|
||||||
|
"border": "none",
|
||||||
|
"color": "#FFFFFF",
|
||||||
|
"cursor": "pointer",
|
||||||
|
"display": "block",
|
||||||
|
"fontSize": "1em",
|
||||||
|
"lineHeight": "1.5",
|
||||||
|
"marginBottom": "0.6em",
|
||||||
|
"padding": "0px 5px",
|
||||||
|
"textAlign": "left",
|
||||||
|
"width": "100%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
▲ 2 stack frames were expanded.
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
</StackTrace>
|
||||||
|
</div>
|
||||||
|
</StackTraceTab>
|
||||||
|
`;
|
Loading…
Reference in New Issue
Block a user