Move StackTraceTab and react-error-overlay

From zalmoxisus/remotedev-app/pull/43
This commit is contained in:
Zalmoxisus 2018-12-11 22:26:24 +02:00
parent faea737868
commit 5a2c1ee125
30 changed files with 3679 additions and 96 deletions

View 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" ]
}

View File

@ -0,0 +1,5 @@
node_modules
build
dev
dist
lib

View 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"
]
}

View 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.

View 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

View 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;

View 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"
}
}

View 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>
)
}
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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,
};

View 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 };

View 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 };

View 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;

View 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;

View 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;

View 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;

View 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 };

View 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;

View 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 };

View 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;

View 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;

View 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;

View 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');

View 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;

View 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;

1814
yarn.lock

File diff suppressed because it is too large Load Diff