Permalink
Browse files

feat(links): open files in configured editor

If filenames are absolute the generated links can be opened
in an editor if the `editorScheme` property is set.

If `react-transform-catch-errors` passes in a `filename` this
is used for the top function if the stack trace filename
isn't absolute

Closes: #10
  • Loading branch information...
stevoland authored and davidpfahler committed Oct 30, 2015
1 parent f0229c5 commit 6b44f19fd47242eafd297c9259addd21df72eae9
Showing with 494 additions and 13 deletions.
  1. +4 −1 .babelrc
  2. +20 −0 README.md
  3. +3 −2 package.json
  4. +25 −4 src/index.js
  5. +63 −0 src/lib.js
  6. +4 −4 tests/errorStackParserMock.js
  7. +14 −1 tests/framesStub.json
  8. +14 −0 tests/framesStubAbsoluteFilenames.json
  9. +204 −1 tests/index.js
  10. +143 −0 tests/lib.js
View
@@ -1,4 +1,7 @@
{
"stage": 0,
"loose": "all"
"loose": "all",
"plugins": [
"rewire"
]
}
View
@@ -46,3 +46,23 @@ An error that's only in the console is only half the fun. Now you can use all th
## Will this catch errors for me?
No. As you can see above, this is only a UI component for rendering errors and their stack traces. It's works great with other solutions, that automate the error catching for you, see the [examples](https://github.com/KeywordBrain/redbox-react/tree/master/examples).
## Optional props
`editorScheme` `[?string]` If a filename in the stack trace is local, the component can create the
link to open your editor using this scheme eg: `subl` to create `subl://open?url=file:///filename`.
`useLines` `[boolean=true]` Line numbers in the stack trace may be unreliable depending on the
type of sourcemaps. You can choose to not display them with this flag.
`useColumns` `[boolean=true]` Column numbers in the stack trace may be unreliable depending on the
type of sourcemaps. You can choose to not display them with this flag.
If using [react-transform-catch-errors](https://github.com/gaearon/react-transform-catch-errors#installation) you can add these options to your `.babelrc` through the [`imports` property](https://github.com/gaearon/react-transform-catch-errors#installation).
## Sourcemaps with Webpack
If using [Webpack](https://webpack.github.io) you can get accurate filenames in the stacktrace by
setting the `output.devtoolModuleFilenameTemplate` settings to `/[absolute-resource-path]`.
It's recommended to set `devtool` setting to `'eval'`.
View
@@ -34,13 +34,14 @@
"babel-core": "^5.6.18",
"babel-eslint": "^3.1.15",
"babel-loader": "^5.1.4",
"babel-plugin-rewire": "^0.1.22",
"rimraf": "^2.3.4",
"semantic-release": "^4.0.0",
"standard": "^5.0.0-2",
"tap-spec": "^4.0.2",
"tape": "^4.0.1",
"webpack": "^1.9.6",
"webpack-dev-server": "^1.8.2",
"semantic-release": "^4.0.0"
"webpack-dev-server": "^1.8.2"
},
"peerDependencies": {
"react": ">=0.13.2 || ^0.14.0-rc1"
View
@@ -2,23 +2,44 @@ import React, {Component, PropTypes} from 'react'
import style from './style.js'
import ErrorStackParser from 'error-stack-parser'
import assign from 'object-assign'
import {isFilenameAbsolute, makeUrl, makeLinkText} from './lib'
export default class RedBox extends Component {
static propTypes = {
error: PropTypes.instanceOf(Error).isRequired
error: PropTypes.instanceOf(Error).isRequired,
filename: PropTypes.string,
editorScheme: PropTypes.string,
useLines: PropTypes.bool,
useColumns: PropTypes.bool
}
static displayName = 'RedBox'
static defaultProps = {
useLines: true,
useColumns: true
}
render () {
const {error} = this.props
const {error, filename, editorScheme, useLines, useColumns} = this.props
const {redbox, message, stack, frame, file, linkToFile} = assign({}, style, this.props.style)
const frames = ErrorStackParser.parse(error).map((f, index) => {
const link = `${f.fileName}:${f.lineNumber}:${f.columnNumber}`
let text
let url
if (index === 0 && filename && !isFilenameAbsolute(f.fileName)) {
url = makeUrl(filename, editorScheme)
text = makeLinkText(filename)
} else {
let lines = useLines ? f.lineNumber : null
let columns = useColumns ? f.columnNumber : null
url = makeUrl(f.fileName, editorScheme, lines, columns)
text = makeLinkText(f.fileName, lines, columns)
}
return (
<div style={frame} key={index}>
<div>{f.functionName}</div>
<div style={file}>
<a href={link} style={linkToFile}>{link}</a>
<a href={url} style={linkToFile}>{text}</a>
</div>
</div>
)
View
@@ -0,0 +1,63 @@
export const filenameWithoutLoaders = (filename) => {
var index = filename.lastIndexOf('!')
return index < 0 ? filename : filename.substr(index + 1)
}
export const filenameHasLoaders = (filename) => {
const actualFilename = filenameWithoutLoaders(filename)
return actualFilename !== filename
}
export const filenameHasSchema = (filename) => {
return /^[\w]+\:/.test(filename)
}
export const isFilenameAbsolute = (filename) => {
const actualFilename = filenameWithoutLoaders(filename)
if (actualFilename.indexOf('/') === 0) {
return true
}
return false
}
export const makeUrl = (filename, scheme, line, column) => {
let actualFilename = filenameWithoutLoaders(filename)
if (filenameHasSchema(filename)) {
return actualFilename
}
let url = `file://${actualFilename}`
if (scheme) {
url = `${scheme}://open?url=${url}`
if (line && actualFilename === filename) {
url = `${url}&line=${line}`
if (column) {
url = `${url}&column=${column}`
}
}
}
return url
}
export const makeLinkText = (filename, line, column) => {
let text = filenameWithoutLoaders(filename)
if (line && text === filename) {
text = `${text}:${line}`
if (column) {
text = `${text}:${column}`
}
}
return text
}
@@ -1,5 +1,5 @@
import framesStub from './framesStub.json'
export default {
parse: () => framesStub
export default (framesStub) => {
return {
parse: () => framesStub
}
}
View
@@ -1 +1,14 @@
[{"functionName":"App.render","fileName":"webpack:///./components/App.js?","lineNumber":45,"columnNumber":12},{"functionName":"App.render","fileName":"webpack:///./~/react-hot-loader/~/react-hot-api/modules/makeAssimilatePrototype.js?","lineNumber":17,"columnNumber":41},{"functionName":"ReactCompositeComponentMixin._renderValidatedComponentWithoutOwnerOrContext","fileName":"webpack:///./~/react/lib/ReactCompositeComponent.js?","lineNumber":789,"columnNumber":34},{"functionName":"ReactCompositeComponentMixin._renderValidatedComponent","fileName":"webpack:///./~/react/lib/ReactCompositeComponent.js?","lineNumber":816,"columnNumber":14},{"functionName":"ReactPerf.measure.wrapper","fileName":"webpack:///./~/react/lib/ReactPerf.js?","lineNumber":70,"columnNumber":21},{"functionName":"ReactCompositeComponentMixin.mountComponent","fileName":"webpack:///./~/react/lib/ReactCompositeComponent.js?","lineNumber":237,"columnNumber":30},{"functionName":"ReactPerf.measure.wrapper","fileName":"webpack:///./~/react/lib/ReactPerf.js?","lineNumber":70,"columnNumber":21},{"functionName":"Object.ReactReconciler.mountComponent","fileName":"webpack:///./~/react/lib/ReactReconciler.js?","lineNumber":38,"columnNumber":35},{"functionName":"mountComponentIntoNode","fileName":"webpack:///./~/react/lib/ReactMount.js?","lineNumber":248,"columnNumber":32},{"functionName":"ReactReconcileTransaction.Mixin.perform","fileName":"webpack:///./~/react/lib/Transaction.js?","lineNumber":134,"columnNumber":20}]
[
{
"functionName": "App.render",
"fileName": "webpack:\/\/\/.\/components\/App.js?",
"lineNumber": 45,
"columnNumber": 12
},
{
"functionName": "App.render",
"fileName": "webpack:\/\/\/.\/~\/react-hot-loader\/~\/react-hot-api\/modules\/makeAssimilatePrototype.js?",
"lineNumber": 17,
"columnNumber": 41
}
]
@@ -0,0 +1,14 @@
[
{
"functionName": "App.render",
"fileName": "\/components\/App.js",
"lineNumber": 45,
"columnNumber": 12
},
{
"functionName": "App.render",
"fileName": "\/some-path\/modules\/makeAssimilatePrototype.js",
"lineNumber": 17,
"columnNumber": 41
}
]
Oops, something went wrong.

0 comments on commit 6b44f19

Please sign in to comment.