mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-25 11:03:57 +03:00
Move StackTraceTab and react-error-overlay
From zalmoxisus/remotedev-app/pull/43
This commit is contained in:
parent
671b26157e
commit
d6a0e13d3b
9
packages/redux-devtools-trace-monitor/.babelrc
Normal file
9
packages/redux-devtools-trace-monitor/.babelrc
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"presets": [ ["env", {
|
||||
"targets": {
|
||||
"firefox" : 56,
|
||||
"chrome" : 64
|
||||
}
|
||||
}], "react", "flow"],
|
||||
"plugins": [ "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
|
36
packages/redux-devtools-trace-monitor/.eslintrc
Normal file
36
packages/redux-devtools-trace-monitor/.eslintrc
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"extends": "eslint-config-airbnb",
|
||||
"globals": {
|
||||
"chrome": true
|
||||
},
|
||||
"env": {
|
||||
"jest": true,
|
||||
"browser": true,
|
||||
"node": 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,
|
||||
"block-scoped-var": 0,
|
||||
"padded-blocks": 0,
|
||||
"quotes": [ 1, "single" ],
|
||||
"comma-style": [ 2, "last" ],
|
||||
"eol-last": 0,
|
||||
"no-unused-vars": 0,
|
||||
"no-console": 0,
|
||||
"func-names": 0,
|
||||
"prefer-const": 0,
|
||||
"comma-dangle": 0,
|
||||
"id-length": 0,
|
||||
"no-use-before-define": 0,
|
||||
"indent": [2, 2, {"SwitchCase": 1}],
|
||||
"new-cap": [2, { "capIsNewExceptions": ["Test"] }]
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
]
|
||||
}
|
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
|
57
packages/redux-devtools-trace-monitor/index.js
Normal file
57
packages/redux-devtools-trace-monitor/index.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import InspectorMonitor from 'remotedev-inspector-monitor';
|
||||
import StackTraceTab from './StackTraceTab';
|
||||
import { DATA_TYPE_KEY } from '../../../constants/dataTypes';
|
||||
import SubTabs from './SubTabs';
|
||||
import TestTab from './TestTab';
|
||||
|
||||
const DEFAULT_TABS = [{
|
||||
name: 'Action',
|
||||
component: SubTabs
|
||||
}, {
|
||||
name: 'State',
|
||||
component: SubTabs
|
||||
}, {
|
||||
name: 'Diff',
|
||||
component: SubTabs
|
||||
}];
|
||||
|
||||
const NON_INIT_TABS = [
|
||||
{ name: 'Trace', component: StackTraceTab }
|
||||
];
|
||||
|
||||
class InspectorWrapper extends Component {
|
||||
static update = InspectorMonitor.update;
|
||||
|
||||
render() {
|
||||
const { lib, ...rest } = this.props;
|
||||
console.log(rest);
|
||||
let tabs;
|
||||
if (lib === 'redux') {
|
||||
tabs = () => [
|
||||
...DEFAULT_TABS,
|
||||
...(!rest.monitorState || rest.monitorState.selectedActionId === null ? NON_INIT_TABS : []),
|
||||
{ name: 'Test', component: TestTab }
|
||||
];
|
||||
} else {
|
||||
tabs = () => DEFAULT_TABS;
|
||||
}
|
||||
|
||||
return (
|
||||
<InspectorMonitor
|
||||
dataTypeKey={DATA_TYPE_KEY}
|
||||
shouldPersistState={false}
|
||||
invertTheme={false}
|
||||
theme="nicinabox"
|
||||
tabs={tabs}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWrapper.propTypes = {
|
||||
lib: PropTypes.string
|
||||
};
|
||||
|
||||
export default InspectorWrapper;
|
60
packages/redux-devtools-trace-monitor/package.json
Normal file
60
packages/redux-devtools-trace-monitor/package.json
Normal file
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"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",
|
||||
"postinstall": "npm run build",
|
||||
"prepublishOnly": "npm run lint && npm run test && npm run clean && npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.10.1",
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-eslint": "^6.0.5",
|
||||
"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",
|
||||
"enzyme": "^2.6.0",
|
||||
"enzyme-to-json": "^1.3.0",
|
||||
"eslint": "^2.13.1",
|
||||
"eslint-config-airbnb": "^9.0.1",
|
||||
"eslint-plugin-import": "^1.9.2",
|
||||
"eslint-plugin-jsx-a11y": "^1.5.3",
|
||||
"eslint-plugin-react": "^5.2.2",
|
||||
"jest": "^17.0.3",
|
||||
"react-addons-test-utils": "^15.4.0",
|
||||
"react-dom": "^15.4.0",
|
||||
"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",
|
||||
"settle-promise": "^1.0.0"
|
||||
}
|
||||
}
|
141
packages/redux-devtools-trace-monitor/src/StackTraceTab.js
Normal file
141
packages/redux-devtools-trace-monitor/src/StackTraceTab.js
Normal file
|
@ -0,0 +1,141 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
// import ErrorStackParser from "error-stack-parser";
|
||||
|
||||
import {getStackFrames} from "./react-error-overlay/utils/getStackFrames";
|
||||
import StackTrace from "./react-error-overlay/containers/StackTrace";
|
||||
|
||||
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 => {
|
||||
console.log("Stack frames: ", stackFrames);
|
||||
this.setState({stackFrames, currentError : deserializedError});
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setState({stackFrames : []})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
onStackFrameClicked = (i) => {
|
||||
const stackFrame = this.state.stackFrames[i];
|
||||
|
||||
if(stackFrame) {
|
||||
const parsedFramesNoSourcemaps = ErrorStackParser.parse(this.state.currentError)
|
||||
console.log("Parsed stack frames: ", parsedFramesNoSourcemaps);
|
||||
|
||||
if(chrome && chrome.devtools.panels.openResource) {
|
||||
const frameWithoutSourcemap = parsedFramesNoSourcemaps[i];
|
||||
const {fileName, lineNumber} = frameWithoutSourcemap;
|
||||
console.log("Parsed stack frame: ", stackFrame);
|
||||
console.log("Original stack frame: ", frameWithoutSourcemap);
|
||||
|
||||
const adjustedLineNumber = Math.max(lineNumber - 1, 0);
|
||||
|
||||
|
||||
chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (...callbackArgs) => {
|
||||
console.log("openResource callback args: ", callbackArgs);
|
||||
//console.log("Testing");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
onStackLocationClicked = (fileLocation = {}) => {
|
||||
//console.log("Stack location args: ", ...args);
|
||||
|
||||
// const parsedFramesNoSourcemaps = ErrorStackParser.parse(this.state.currentError)
|
||||
//console.log("Parsed stack frames: ", parsedFramesNoSourcemaps);
|
||||
|
||||
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);
|
||||
*/
|
||||
const adjustedLineNumber = Math.max(lineNumber - 1, 0);
|
||||
|
||||
|
||||
chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (result) => {
|
||||
//console.log("openResource callback args: ", callbackArgs);
|
||||
//console.log("Testing");
|
||||
if(result.isError) {
|
||||
const {fileName : finalFileName, lineNumber : finalLineNumber} = matchingStackFrame;
|
||||
const adjustedLineNumber = Math.max(finalLineNumber - 1, 0);
|
||||
chrome.devtools.panels.openResource(finalFileName, adjustedLineNumber, (result) => {
|
||||
//console.log("openResource result: ", result);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {stackFrames} = this.state;
|
||||
|
||||
return (
|
||||
<div style={{backgroundColor : "white", color : "black"}}>
|
||||
<h2>Dispatched Action Stack Trace</h2>
|
||||
<div style={{display : "flex", flexDirection : "column"}}>
|
||||
<StackTrace
|
||||
stackFrames={stackFrames}
|
||||
errorName={"N/A"}
|
||||
contextSize={3}
|
||||
editorHandler={this.onStackLocationClicked}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
53
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/CodeBlock.js
vendored
Normal file
53
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/CodeBlock.js
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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 { redTransparent, yellowTransparent } from '../styles';
|
||||
|
||||
const _preStyle = {
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
padding: '0.5em',
|
||||
marginTop: '0.5em',
|
||||
marginBottom: '0.5em',
|
||||
overflowX: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
borderRadius: '0.25rem',
|
||||
};
|
||||
|
||||
const primaryPreStyle = {
|
||||
..._preStyle,
|
||||
backgroundColor: redTransparent,
|
||||
};
|
||||
|
||||
const secondaryPreStyle = {
|
||||
..._preStyle,
|
||||
backgroundColor: yellowTransparent,
|
||||
};
|
||||
|
||||
const codeStyle = {
|
||||
fontFamily: 'Consolas, Menlo, monospace',
|
||||
};
|
||||
|
||||
type CodeBlockPropsType = {|
|
||||
main: boolean,
|
||||
codeHTML: string,
|
||||
|};
|
||||
|
||||
function CodeBlock(props: CodeBlockPropsType) {
|
||||
const preStyle = props.main ? primaryPreStyle : secondaryPreStyle;
|
||||
const codeBlock = { __html: props.codeHTML };
|
||||
|
||||
return (
|
||||
<pre style={preStyle}>
|
||||
<code style={codeStyle} dangerouslySetInnerHTML={codeBlock} />
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
export default CodeBlock;
|
86
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/Collapsible.js
vendored
Normal file
86
packages/redux-devtools-trace-monitor/src/react-error-overlay/components/Collapsible.js
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* 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 { black } from '../styles';
|
||||
|
||||
import type { Element as ReactElement } from 'react';
|
||||
|
||||
const _collapsibleStyle = {
|
||||
color: black,
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
background: '#fff',
|
||||
fontFamily: 'Consolas, Menlo, monospace',
|
||||
fontSize: '1em',
|
||||
padding: '0px',
|
||||
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: true,
|
||||
};
|
||||
|
||||
toggleCollaped = () => {
|
||||
this.setState(state => ({
|
||||
collapsed: !state.collapsed,
|
||||
}));
|
||||
};
|
||||
|
||||
render() {
|
||||
const count = this.props.children.length;
|
||||
const collapsed = this.state.collapsed;
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={this.toggleCollaped}
|
||||
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.toggleCollaped}
|
||||
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 { darkGray } from '../styles';
|
||||
|
||||
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: darkGray,
|
||||
cursor: 'pointer',
|
||||
};
|
||||
|
||||
const codeAnchorStyle = {
|
||||
cursor: 'pointer',
|
||||
};
|
||||
|
||||
const toggleStyle = {
|
||||
marginBottom: '1.5em',
|
||||
color: darkGray,
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
textAlign: 'left',
|
||||
background: '#fff',
|
||||
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 { primaryErrorStyle, secondaryErrorStyle } from '../styles';
|
||||
import generateAnsiHTML from '../utils/generateAnsiHTML';
|
||||
|
||||
import { codeFrameColumns } from '@babel/code-frame';
|
||||
|
||||
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, main ? primaryErrorStyle : secondaryErrorStyle);
|
||||
// eslint-disable-next-line
|
||||
break oLoop;
|
||||
}
|
||||
}
|
||||
|
||||
return <CodeBlock main={main} codeHTML={code.innerHTML} />;
|
||||
}
|
||||
|
||||
export default StackFrameCodeBlock;
|
94
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackTrace.js
vendored
Normal file
94
packages/redux-devtools-trace-monitor/src/react-error-overlay/containers/StackTrace.js
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* 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;
|
||||
|
||||
stackFrames.forEach((frame, index) => {
|
||||
const { fileName, _originalFileName: sourceFileName } = frame;
|
||||
const isInternalUrl = isInternalFile(sourceFileName, fileName);
|
||||
const isThrownIntentionally = !isBultinErrorName(errorName);
|
||||
const shouldCollapse =
|
||||
isInternalUrl && (isThrownIntentionally || hasReachedAppCode);
|
||||
|
||||
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 key={'bundle-' + bundleCount}>
|
||||
{currentBundle}
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
currentBundle = [];
|
||||
}
|
||||
|
||||
if (!shouldCollapse) {
|
||||
renderedFrames.push(frameEle);
|
||||
}
|
||||
});
|
||||
|
||||
return renderedFrames;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={traceStyle}>{this.renderFrames()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default StackTrace;
|
54
packages/redux-devtools-trace-monitor/src/react-error-overlay/styles.js
vendored
Normal file
54
packages/redux-devtools-trace-monitor/src/react-error-overlay/styles.js
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* 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 */
|
||||
const black = '#293238',
|
||||
darkGray = '#878e91',
|
||||
red = '#ce1126',
|
||||
redTransparent = 'rgba(206, 17, 38, 0.05)',
|
||||
lightRed = '#fccfcf',
|
||||
yellow = '#fbf5b4',
|
||||
yellowTransparent = 'rgba(251, 245, 180, 0.3)',
|
||||
white = '#ffffff';
|
||||
|
||||
const iframeStyle = {
|
||||
position: 'fixed',
|
||||
top: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
'z-index': 2147483647,
|
||||
};
|
||||
|
||||
const overlayStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
'box-sizing': 'border-box',
|
||||
'text-align': 'center',
|
||||
'background-color': white,
|
||||
};
|
||||
|
||||
const primaryErrorStyle = {
|
||||
'background-color': lightRed,
|
||||
};
|
||||
|
||||
const secondaryErrorStyle = {
|
||||
'background-color': yellow,
|
||||
};
|
||||
|
||||
export {
|
||||
iframeStyle,
|
||||
overlayStyle,
|
||||
primaryErrorStyle,
|
||||
secondaryErrorStyle,
|
||||
black,
|
||||
darkGray,
|
||||
red,
|
||||
redTransparent,
|
||||
yellowTransparent,
|
||||
};
|
41
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/absolutifyCaret.js
vendored
Normal file
41
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/dom/absolutifyCaret.js
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* 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';
|
||||
// $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 };
|
107
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/generateAnsiHTML.js
vendored
Normal file
107
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/generateAnsiHTML.js
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* 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 { AllHtmlEntities as Entities } from 'html-entities';
|
||||
|
||||
var entities = new Entities();
|
||||
|
||||
// Color scheme inspired by https://chriskempson.github.io/base16/css/base16-github.css
|
||||
// var base00 = 'ffffff'; // Default Background
|
||||
//var base01 = 'f5f5f5'; // Lighter Background (Used for status bars)
|
||||
var base01 = "red";
|
||||
// var base02 = 'c8c8fa'; // Selection Background
|
||||
var base03 = '6e6e6e'; // Comments, Invisibles, Line Highlighting
|
||||
// var base04 = 'e8e8e8'; // Dark Foreground (Used for status bars)
|
||||
var base05 = '333333'; // Default Foreground, Caret, Delimiters, Operators
|
||||
// var base06 = 'ffffff'; // Light Foreground (Not often used)
|
||||
// var base07 = 'ffffff'; // Light Background (Not often used)
|
||||
var base08 = '881280'; // Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted
|
||||
// var base09 = '0086b3'; // Integers, Boolean, Constants, XML Attributes, Markup Link Url
|
||||
// var base0A = '795da3'; // Classes, Markup Bold, Search Text Background
|
||||
var base0B = '1155cc'; // Strings, Inherited Class, Markup Code, Diff Inserted
|
||||
var base0C = '994500'; // Support, Regular Expressions, Escape Characters, Markup Quotes
|
||||
// var base0D = '795da3'; // Functions, Methods, Attribute IDs, Headings
|
||||
var base0E = 'c80000'; // Keywords, Storage, Selector, Markup Italic, Diff Changed
|
||||
// var base0F = '333333'; // Deprecated, Opening/Closing Embedded Language Tags e.g. <?php ?>
|
||||
|
||||
// Map ANSI colors from what babel-code-frame uses to base16-github
|
||||
// See: https://github.com/babel/babel/blob/e86f62b304d280d0bab52c38d61842b853848ba6/packages/babel-code-frame/src/index.js#L9-L22
|
||||
var colors = {
|
||||
reset: [base05, 'transparent'],
|
||||
black: base05,
|
||||
red: base08 /* marker, bg-invalid */,
|
||||
green: base0B /* string */,
|
||||
yellow: base08 /* capitalized, jsx_tag, punctuator */,
|
||||
blue: base0C,
|
||||
magenta: base0C /* regex */,
|
||||
cyan: base0E /* keyword */,
|
||||
gray: base03 /* comment, gutter */,
|
||||
lightgrey: base01,
|
||||
darkgrey: base03,
|
||||
};
|
||||
|
||||
var anserMap = {
|
||||
'ansi-bright-black': 'black',
|
||||
'ansi-bright-yellow': 'yellow',
|
||||
'ansi-yellow': 'yellow',
|
||||
'ansi-bright-green': 'green',
|
||||
'ansi-green': 'green',
|
||||
'ansi-bright-cyan': 'cyan',
|
||||
'ansi-cyan': 'cyan',
|
||||
'ansi-bright-red': 'red',
|
||||
'ansi-red': 'red',
|
||||
'ansi-bright-magenta': 'magenta',
|
||||
'ansi-magenta': 'magenta',
|
||||
'ansi-white': 'darkgrey',
|
||||
};
|
||||
|
||||
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 = colors[anserMap[fg]];
|
||||
if (color != null) {
|
||||
result += '<span style="color: #' + color + ';">' + part + '</span>';
|
||||
} else {
|
||||
if (fg != null) {
|
||||
console.log('Missing color mapping: ', fg);
|
||||
}
|
||||
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;
|
48
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getStackFrames.js
vendored
Normal file
48
packages/redux-devtools-trace-monitor/src/react-error-overlay/utils/getStackFrames.js
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
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 }) =>
|
||||
functionName == null ||
|
||||
functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
Loading…
Reference in New Issue
Block a user