├── .babelrc ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .travis.yml ├── README.md ├── lerna.json ├── package.json ├── packages ├── ansi-html-themed │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── yarn.lock ├── stack-frame-mapper │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── fixtures │ │ ├── bundle-default.json │ │ ├── bundle.js │ │ ├── bundle.js.map │ │ └── bundle.json │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── bundle.js │ │ │ └── setupJest.js │ │ └── index.js │ └── yarn.lock ├── stack-frame-overlay │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── mount.js │ │ │ └── setupJest.js │ │ ├── components │ │ │ ├── additional.js │ │ │ ├── close.js │ │ │ ├── code.js │ │ │ ├── footer.js │ │ │ ├── frame.js │ │ │ ├── frames.js │ │ │ └── overlay.js │ │ ├── effects │ │ │ ├── proxyConsole.js │ │ │ ├── shortcuts.js │ │ │ ├── stackTraceLimit.js │ │ │ ├── unhandledError.js │ │ │ └── unhandledRejection.js │ │ ├── index.js │ │ ├── overlay.js │ │ ├── styles.js │ │ └── utils │ │ │ ├── dom │ │ │ ├── absolutifyCaret.js │ │ │ ├── consumeEvent.js │ │ │ ├── css.js │ │ │ └── enableTabClick.js │ │ │ ├── errorRegister.js │ │ │ └── isInternalFile.js │ └── yarn.lock ├── stack-frame-parser │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── chrome.js │ │ │ ├── firefox.js │ │ │ ├── generic.js │ │ │ ├── react.js │ │ │ └── safari.js │ │ └── index.js │ └── yarn.lock ├── stack-frame-unmapper │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── fixtures │ │ ├── bundle.js │ │ ├── bundle.js.map │ │ └── bundle.json │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── bundle.js │ │ │ └── setupJest.js │ │ └── index.js │ └── yarn.lock ├── stack-frame-utils │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── fixtures │ │ ├── bundle.js │ │ ├── bundle.js.map │ │ ├── inline.es6.js │ │ ├── inline.js │ │ └── junk-inline.js │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── lines-around.js.snap │ │ │ ├── extract-source-map.js │ │ │ ├── get-source-map.js │ │ │ ├── lines-around.js │ │ │ └── setupJest.js │ │ ├── getLinesAround.js │ │ ├── getSourceMap.js │ │ └── index.js │ └── yarn.lock └── stack-frame │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── script-line.js.snap │ │ │ └── stack-frame.js.snap │ │ ├── script-line.js │ │ └── stack-frame.js │ └── index.js │ └── yarn.lock ├── tasks └── test.sh └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "ie": 9, 6 | "uglify": true 7 | }, 8 | "useBuiltIns": true 9 | }] 10 | ], 11 | "plugins": [ 12 | "transform-flow-strip-types", 13 | ["transform-runtime", { 14 | "helpers": false, 15 | "polyfill": false, 16 | "regenerator": true 17 | }] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | parser: 'babel-eslint', 5 | 6 | plugins: ['import', 'flowtype'], 7 | 8 | env: { 9 | browser: true, 10 | es6: true, 11 | jest: true, 12 | node: true, 13 | }, 14 | 15 | parserOptions: { 16 | ecmaVersion: 6, 17 | sourceType: 'module', 18 | ecmaFeatures: { 19 | generators: true, 20 | experimentalObjectRestSpread: true, 21 | }, 22 | }, 23 | 24 | settings: { 25 | 'import/ignore': ['node_modules'], 26 | 'import/extensions': ['.js'], 27 | 'import/resolver': { 28 | node: { 29 | extensions: ['.js', '.json'], 30 | }, 31 | }, 32 | }, 33 | 34 | rules: { 35 | 'array-callback-return': 'warn', 36 | 'curly': ['warn', 'all'], 37 | 'default-case': ['warn', { commentPattern: '^no default$' }], 38 | 'dot-location': ['warn', 'property'], 39 | eqeqeq: ['warn', 'allow-null'], 40 | 'new-parens': 'warn', 41 | 'no-array-constructor': 'warn', 42 | 'no-caller': 'warn', 43 | 'no-cond-assign': ['warn', 'always'], 44 | 'no-const-assign': 'warn', 45 | 'no-control-regex': 'warn', 46 | 'no-delete-var': 'warn', 47 | 'no-dupe-args': 'warn', 48 | 'no-dupe-class-members': 'warn', 49 | 'no-dupe-keys': 'warn', 50 | 'no-duplicate-case': 'warn', 51 | 'no-empty-character-class': 'warn', 52 | 'no-empty-pattern': 'warn', 53 | 'no-empty': 'warn', 54 | 'no-eval': 'warn', 55 | 'no-ex-assign': 'warn', 56 | 'no-extend-native': 'warn', 57 | 'no-extra-bind': 'warn', 58 | 'no-extra-label': 'warn', 59 | 'no-fallthrough': 'warn', 60 | 'no-func-assign': 'warn', 61 | 'no-implied-eval': 'warn', 62 | 'no-invalid-regexp': 'warn', 63 | 'no-iterator': 'warn', 64 | 'no-label-var': 'warn', 65 | 'no-labels': ['warn', { allowLoop: true, allowSwitch: false }], 66 | 'no-lone-blocks': 'warn', 67 | 'no-loop-func': 'warn', 68 | 'no-mixed-operators': [ 69 | 'warn', 70 | { 71 | groups: [ 72 | ['&', '|', '^', '~', '<<', '>>', '>>>'], 73 | ['==', '!=', '===', '!==', '>', '>=', '<', '<='], 74 | ['&&', '||'], 75 | ['in', 'instanceof'], 76 | ], 77 | allowSamePrecedence: false, 78 | }, 79 | ], 80 | 'no-multi-str': 'warn', 81 | 'no-native-reassign': 'warn', 82 | 'no-negated-in-lhs': 'warn', 83 | 'no-new-func': 'warn', 84 | 'no-new-object': 'warn', 85 | 'no-new-symbol': 'warn', 86 | 'no-new-wrappers': 'warn', 87 | 'no-obj-calls': 'warn', 88 | 'no-octal': 'warn', 89 | 'no-octal-escape': 'warn', 90 | 'no-redeclare': 'warn', 91 | 'no-regex-spaces': 'warn', 92 | 'no-restricted-syntax': ['error', 'WithStatement', 'ForOfStatement'], 93 | 'no-script-url': 'warn', 94 | 'no-self-assign': 'warn', 95 | 'no-self-compare': 'warn', 96 | 'no-sequences': 'warn', 97 | 'no-shadow-restricted-names': 'warn', 98 | 'no-sparse-arrays': 'warn', 99 | 'no-template-curly-in-string': 'warn', 100 | 'no-this-before-super': 'warn', 101 | 'no-throw-literal': 'warn', 102 | 'no-undef': 'error', 103 | 'no-restricted-globals': ['error', 'event', 'document'], 104 | 'no-unexpected-multiline': 'warn', 105 | 'no-unreachable': 'warn', 106 | 'no-unused-expressions': [ 107 | 'warn', 108 | { 109 | allowShortCircuit: true, 110 | allowTernary: true, 111 | }, 112 | ], 113 | 'no-unused-labels': 'warn', 114 | 'no-unused-vars': [ 115 | 'warn', 116 | { 117 | vars: 'local', 118 | varsIgnorePattern: '^_', 119 | args: 'none', 120 | ignoreRestSiblings: true, 121 | }, 122 | ], 123 | 'no-use-before-define': ['warn', 'nofunc'], 124 | 'no-useless-computed-key': 'warn', 125 | 'no-useless-concat': 'warn', 126 | 'no-useless-constructor': 'warn', 127 | 'no-useless-escape': 'warn', 128 | 'no-useless-rename': [ 129 | 'warn', 130 | { 131 | ignoreDestructuring: false, 132 | ignoreImport: false, 133 | ignoreExport: false, 134 | }, 135 | ], 136 | 'no-with': 'warn', 137 | 'no-whitespace-before-property': 'warn', 138 | 'no-var': 'error', 139 | 'operator-assignment': ['warn', 'always'], 140 | radix: 'warn', 141 | 'require-yield': 'warn', 142 | 'rest-spread-spacing': ['warn', 'never'], 143 | strict: ['warn', 'never'], 144 | 'unicode-bom': ['warn', 'never'], 145 | 'use-isnan': 'warn', 146 | 'valid-typeof': 'warn', 147 | 'no-restricted-properties': [ 148 | 'error', 149 | { 150 | object: 'require', 151 | property: 'ensure', 152 | message: 'Please use import() instead. More info: https://webpack.js.org/guides/code-splitting-import/#dynamic-import', 153 | }, 154 | { 155 | object: 'System', 156 | property: 'import', 157 | message: 'Please use import() instead. More info: https://webpack.js.org/guides/code-splitting-import/#dynamic-import', 158 | }, 159 | ], 160 | 161 | 'import/default': 'warn', 162 | 'import/export': 'warn', 163 | 'import/named': 'warn', 164 | 'import/namespace': 'warn', 165 | 'import/no-amd': 'warn', 166 | 'import/no-duplicates': 'warn', 167 | 'import/no-extraneous-dependencies': 'warn', 168 | 'import/no-named-as-default': 'warn', 169 | 'import/no-named-as-default-member': 'warn', 170 | 'import/no-unresolved': ['warn', { commonjs: true }], 171 | 'import/no-webpack-loader-syntax': 'error', 172 | 173 | 'flowtype/define-flow-type': 'warn', 174 | 'flowtype/require-valid-file-annotation': 'warn', 175 | 'flowtype/use-flow-type': 'warn', 176 | }, 177 | }; 178 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | packages/*/src/**/*.js 5 | 6 | [libs] 7 | 8 | [options] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | 6 4 | before_script: 5 | - export DISPLAY=:99.0 6 | - sh -e /etc/init.d/xvfb start 7 | after_success: 8 | - bash <(curl -s https://codecov.io/bash) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stack-frame 2 | 3 | [![Build Status](https://travis-ci.org/Timer/stack-frame.svg?branch=master)](https://travis-ci.org/Timer/stack-frame) 4 | [![codecov](https://codecov.io/gh/Timer/stack-frame/branch/master/graph/badge.svg)](https://codecov.io/gh/Timer/stack-frame) 5 | 6 | A set of packages for managing stack frames. 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.3", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "0.4.0" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "postinstall": "lerna exec --concurrency=1 -- yarn&&lerna bootstrap", 5 | "release": "lerna publish --exact", 6 | "start": "find ./packages -type d -maxdepth 1 | tail -n +2 | xargs -I {} echo \"cd {} && yarn build -- -w\" | sed -e 's/^/\"/g' -e 's/$/\"/g' | tr '\n' ' ' | xargs concurrently -k", 7 | "lint": "eslint 'packages/*/src/**/*.js'", 8 | "format": "prettier --single-quote --trailing-comma es5 --write 'packages/*/src/**/*.js'", 9 | "test": "tasks/test.sh", 10 | "docs": "lerna exec -- ../../node_modules/.bin/documentation readme src/**/*.js --section=API" 11 | }, 12 | "devDependencies": { 13 | "babel-cli": "^6.24.0", 14 | "babel-eslint": "^7.1.1", 15 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 16 | "babel-plugin-transform-runtime": "^6.23.0", 17 | "babel-preset-env": "^1.2.1", 18 | "concurrently": "^3.4.0", 19 | "documentation": "4.0.0-beta.18", 20 | "eslint": "^3.18.0", 21 | "eslint-plugin-flowtype": "^2.30.3", 22 | "eslint-plugin-import": "^2.2.0", 23 | "jest": "^19.0.2", 24 | "jest-fetch-mock": "^1.0.8", 25 | "lerna": "^2.0.0-rc.3", 26 | "prettier": "^1.2.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/ansi-html-themed/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/ansi-html-themed/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/ansi-html-themed/README.md: -------------------------------------------------------------------------------- 1 | # `ansi-html-themed` 2 | 3 | Turns chalked code into pretty html. 4 | 5 | # API 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/ansi-html-themed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ansi-html-themed", 3 | "version": "0.1.0", 4 | "description": "Turns chalked code into pretty html.", 5 | "scripts": { 6 | "prepublishOnly": "npm run build && npm test", 7 | "build": "../../node_modules/.bin/babel src/ -d lib/", 8 | "test": "../../node_modules/.bin/jest" 9 | }, 10 | "main": "lib/index.js", 11 | "repository": "https://github.com/Timer/stack-frame/tree/master/packages/ansi-html-themed", 12 | "author": "Joe Haddad ", 13 | "license": "MIT", 14 | "files": [ 15 | "lib/" 16 | ], 17 | "dependencies": { 18 | "anser": "^1.2.5" 19 | }, 20 | "devDependencies": { 21 | "chalk": "^1.1.3" 22 | }, 23 | "jest": { 24 | "collectCoverage": true, 25 | "coverageThreshold": { 26 | "global": { 27 | "statements": 100 28 | } 29 | }, 30 | "coverageReporters": [ 31 | "json" 32 | ], 33 | "testMatch": [ 34 | "/src/**/__tests__/**/*.js?(x)", 35 | "/src/**/?(*.)(spec|test).js?(x)" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/ansi-html-themed/src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import Anser from 'anser'; 3 | 4 | const base01 = 'f5f5f5', 5 | base03 = '969896', 6 | base05 = '333333', 7 | base08 = 'ed6a43', 8 | base0B = '183691', 9 | base0C = '183691', 10 | base0E = 'a71d5d'; 11 | 12 | const defaultColors = { 13 | reset: [base05, 'transparent'], 14 | black: base05, 15 | red: base08 /* marker, bg-invalid */, 16 | green: base0B /* string */, 17 | yellow: base08 /* capitalized, jsx_tag, punctuator */, 18 | blue: base0C, 19 | magenta: base0C /* regex */, 20 | cyan: base0E /* keyword */, 21 | gray: base03 /* comment, gutter */, 22 | lightgrey: base01, 23 | darkgrey: base03, 24 | }; 25 | 26 | function generateAnsiHtml(text: string, colors: Object = {}) { 27 | colors = Object.assign({}, defaultColors, colors); 28 | const arr = new Anser().ansiToJson(text, { 29 | use_classes: true, 30 | }); 31 | 32 | let result = ''; 33 | let open = false; 34 | for (let index = 0; index < arr.length; ++index) { 35 | const c = arr[index]; 36 | const { content, fg } = c; 37 | const contentParts = content.split('\n'); 38 | for (let _index = 0; _index < contentParts.length; ++_index) { 39 | if (!open) { 40 | result += ''; 41 | open = true; 42 | } 43 | const part = contentParts[_index].replace('\r', ''); 44 | const color = fg == null 45 | ? null 46 | : colors[fg] || colors[fg.replace(/^ansi-(bright-)?/, '')]; 47 | if (color != null) { 48 | result += '' + part + ''; 49 | } else { 50 | result += '' + part + ''; 51 | } 52 | if (_index < contentParts.length - 1) { 53 | result += ''; 54 | open = false; 55 | result += '
'; 56 | } 57 | } 58 | } 59 | if (open) { 60 | result += ''; 61 | open = false; 62 | } 63 | return result; 64 | } 65 | 66 | export { generateAnsiHtml }; 67 | -------------------------------------------------------------------------------- /packages/ansi-html-themed/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | anser@^1.2.5: 6 | version "1.2.7" 7 | resolved "https://registry.yarnpkg.com/anser/-/anser-1.2.7.tgz#fb1a41a05ffdef5b9e33d5d64794765fd76524e0" 8 | 9 | ansi-regex@^2.0.0: 10 | version "2.1.1" 11 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 12 | 13 | ansi-styles@^2.2.1: 14 | version "2.2.1" 15 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 16 | 17 | chalk@^1.1.3: 18 | version "1.1.3" 19 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 20 | dependencies: 21 | ansi-styles "^2.2.1" 22 | escape-string-regexp "^1.0.2" 23 | has-ansi "^2.0.0" 24 | strip-ansi "^3.0.0" 25 | supports-color "^2.0.0" 26 | 27 | escape-string-regexp@^1.0.2: 28 | version "1.0.5" 29 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 30 | 31 | has-ansi@^2.0.0: 32 | version "2.0.0" 33 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 34 | dependencies: 35 | ansi-regex "^2.0.0" 36 | 37 | strip-ansi@^3.0.0: 38 | version "3.0.1" 39 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 40 | dependencies: 41 | ansi-regex "^2.0.0" 42 | 43 | supports-color@^2.0.0: 44 | version "2.0.0" 45 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 46 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/README.md: -------------------------------------------------------------------------------- 1 | # `stack-frame-mapper` 2 | 3 | Maps a stack frame to a sourcemap. 4 | 5 | # API 6 | 7 | 8 | 9 | ## map 10 | 11 | Enhances a set of [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe)s with their original positions and code (when available). 12 | 13 | **Parameters** 14 | 15 | - `frames` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<StackFrame>** A set of [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe)s which contain (generated) code positions. 16 | - `contextLines` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** The number of lines to provide before and after the line specified in the [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe). (optional, default `3`) 17 | 18 | Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<StackFrame>>** 19 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/fixtures/bundle-default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionName": "App.componentDidMount", 4 | "fileName": "http://localhost:3000/static/js/bundle.js", 5 | "lineNumber": 26122, 6 | "columnNumber": 21, 7 | "_originalFunctionName": "App.componentDidMount", 8 | "_originalFileName": "webpack:///packages/react-scripts/template/src/App.js", 9 | "_originalLineNumber": 7, 10 | "_originalColumnNumber": 0, 11 | "_scriptCode": [ 12 | { 13 | "lineNumber": 26119, 14 | "content": " _createClass(App, [{", 15 | "highlight": false 16 | }, 17 | { 18 | "lineNumber": 26120, 19 | "content": " key: 'componentDidMount',", 20 | "highlight": false 21 | }, 22 | { 23 | "lineNumber": 26121, 24 | "content": " value: function componentDidMount() {", 25 | "highlight": false 26 | }, 27 | { 28 | "lineNumber": 26122, 29 | "content": " document.body.missing();", 30 | "highlight": true 31 | }, 32 | { 33 | "lineNumber": 26123, 34 | "content": " }", 35 | "highlight": false 36 | }, 37 | { 38 | "lineNumber": 26124, 39 | "content": " }, {", 40 | "highlight": false 41 | }, 42 | { 43 | "lineNumber": 26125, 44 | "content": " key: 'render',", 45 | "highlight": false 46 | } 47 | ], 48 | "_originalScriptCode": [ 49 | { 50 | "lineNumber": 4, 51 | "content": "", 52 | "highlight": false 53 | }, 54 | { 55 | "lineNumber": 5, 56 | "content": "class App extends Component {", 57 | "highlight": false 58 | }, 59 | { 60 | "lineNumber": 6, 61 | "content": " componentDidMount() {", 62 | "highlight": false 63 | }, 64 | { 65 | "lineNumber": 7, 66 | "content": " document.body.missing()", 67 | "highlight": true 68 | }, 69 | { 70 | "lineNumber": 8, 71 | "content": " }", 72 | "highlight": false 73 | }, 74 | { 75 | "lineNumber": 9, 76 | "content": "", 77 | "highlight": false 78 | }, 79 | { 80 | "lineNumber": 10, 81 | "content": " render() {", 82 | "highlight": false 83 | } 84 | ] 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/fixtures/bundle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionName": "App.componentDidMount", 4 | "fileName": "http://localhost:3000/static/js/bundle.js", 5 | "lineNumber": 26122, 6 | "columnNumber": 21, 7 | "_originalFunctionName": "App.componentDidMount", 8 | "_originalFileName": "webpack:///packages/react-scripts/template/src/App.js", 9 | "_originalLineNumber": 7, 10 | "_originalColumnNumber": 0, 11 | "_scriptCode": [ 12 | { 13 | "lineNumber": 26122, 14 | "content": " document.body.missing();", 15 | "highlight": true 16 | } 17 | ], 18 | "_originalScriptCode": [ 19 | { 20 | "lineNumber": 7, 21 | "content": " document.body.missing()", 22 | "highlight": true 23 | } 24 | ] 25 | }, 26 | { 27 | "functionName": null, 28 | "fileName": "http://localhost:3000/static/js/bundle.js", 29 | "lineNumber": 30091, 30 | "columnNumber": 25, 31 | "_originalFunctionName": null, 32 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactCompositeComponent.js", 33 | "_originalLineNumber": 265, 34 | "_originalColumnNumber": 0, 35 | "_scriptCode": [ 36 | { 37 | "lineNumber": 30091, 38 | "content": " return inst.componentDidMount();", 39 | "highlight": true 40 | } 41 | ], 42 | "_originalScriptCode": [ 43 | { 44 | "lineNumber": 265, 45 | "content": " return inst.componentDidMount();", 46 | "highlight": true 47 | } 48 | ] 49 | }, 50 | { 51 | "functionName": "measureLifeCyclePerf", 52 | "fileName": "http://localhost:3000/static/js/bundle.js", 53 | "lineNumber": 29901, 54 | "columnNumber": 12, 55 | "_originalFunctionName": "measureLifeCyclePerf", 56 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactCompositeComponent.js", 57 | "_originalLineNumber": 75, 58 | "_originalColumnNumber": 0, 59 | "_scriptCode": [ 60 | { 61 | "lineNumber": 29901, 62 | "content": " return fn();", 63 | "highlight": true 64 | } 65 | ], 66 | "_originalScriptCode": [ 67 | { 68 | "lineNumber": 75, 69 | "content": " return fn();", 70 | "highlight": true 71 | } 72 | ] 73 | }, 74 | { 75 | "functionName": null, 76 | "fileName": "http://localhost:3000/static/js/bundle.js", 77 | "lineNumber": 30090, 78 | "columnNumber": 11, 79 | "_originalFunctionName": null, 80 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactCompositeComponent.js", 81 | "_originalLineNumber": 264, 82 | "_originalColumnNumber": 0, 83 | "_scriptCode": [ 84 | { 85 | "lineNumber": 30090, 86 | "content": " measureLifeCyclePerf(function () {", 87 | "highlight": true 88 | } 89 | ], 90 | "_originalScriptCode": [ 91 | { 92 | "lineNumber": 264, 93 | "content": " measureLifeCyclePerf(function () {", 94 | "highlight": true 95 | } 96 | ] 97 | }, 98 | { 99 | "functionName": "CallbackQueue.notifyAll", 100 | "fileName": "http://localhost:3000/static/js/bundle.js", 101 | "lineNumber": 13256, 102 | "columnNumber": 22, 103 | "_originalFunctionName": "CallbackQueue.notifyAll", 104 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/CallbackQueue.js", 105 | "_originalLineNumber": 76, 106 | "_originalColumnNumber": 0, 107 | "_scriptCode": [ 108 | { 109 | "lineNumber": 13256, 110 | "content": " callbacks[i].call(contexts[i], arg);", 111 | "highlight": true 112 | } 113 | ], 114 | "_originalScriptCode": [ 115 | { 116 | "lineNumber": 76, 117 | "content": " callbacks[i].call(contexts[i], arg);", 118 | "highlight": true 119 | } 120 | ] 121 | }, 122 | { 123 | "functionName": "ReactReconcileTransaction.close", 124 | "fileName": "http://localhost:3000/static/js/bundle.js", 125 | "lineNumber": 35124, 126 | "columnNumber": 26, 127 | "_originalFunctionName": "ReactReconcileTransaction.close", 128 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactReconcileTransaction.js", 129 | "_originalLineNumber": 80, 130 | "_originalColumnNumber": 0, 131 | "_scriptCode": [ 132 | { 133 | "lineNumber": 35124, 134 | "content": " this.reactMountReady.notifyAll();", 135 | "highlight": true 136 | } 137 | ], 138 | "_originalScriptCode": [ 139 | { 140 | "lineNumber": 80, 141 | "content": " this.reactMountReady.notifyAll();", 142 | "highlight": true 143 | } 144 | ] 145 | }, 146 | { 147 | "functionName": "ReactReconcileTransaction.closeAll", 148 | "fileName": "http://localhost:3000/static/js/bundle.js", 149 | "lineNumber": 7390, 150 | "columnNumber": 25, 151 | "_originalFunctionName": "ReactReconcileTransaction.closeAll", 152 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/Transaction.js", 153 | "_originalLineNumber": 206, 154 | "_originalColumnNumber": 0, 155 | "_scriptCode": [ 156 | { 157 | "lineNumber": 7390, 158 | "content": " wrapper.close.call(this, initData);", 159 | "highlight": true 160 | } 161 | ], 162 | "_originalScriptCode": [ 163 | { 164 | "lineNumber": 206, 165 | "content": " wrapper.close.call(this, initData);", 166 | "highlight": true 167 | } 168 | ] 169 | }, 170 | { 171 | "functionName": "ReactReconcileTransaction.perform", 172 | "fileName": "http://localhost:3000/static/js/bundle.js", 173 | "lineNumber": 7337, 174 | "columnNumber": 16, 175 | "_originalFunctionName": "ReactReconcileTransaction.perform", 176 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/Transaction.js", 177 | "_originalLineNumber": 153, 178 | "_originalColumnNumber": 0, 179 | "_scriptCode": [ 180 | { 181 | "lineNumber": 7337, 182 | "content": " this.closeAll(0);", 183 | "highlight": true 184 | } 185 | ], 186 | "_originalScriptCode": [ 187 | { 188 | "lineNumber": 153, 189 | "content": " this.closeAll(0);", 190 | "highlight": true 191 | } 192 | ] 193 | }, 194 | { 195 | "functionName": "batchedMountComponentIntoNode", 196 | "fileName": "http://localhost:3000/static/js/bundle.js", 197 | "lineNumber": 14204, 198 | "columnNumber": 15, 199 | "_originalFunctionName": "batchedMountComponentIntoNode", 200 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", 201 | "_originalLineNumber": 126, 202 | "_originalColumnNumber": 0, 203 | "_scriptCode": [ 204 | { 205 | "lineNumber": 14204, 206 | "content": " transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);", 207 | "highlight": true 208 | } 209 | ], 210 | "_originalScriptCode": [ 211 | { 212 | "lineNumber": 126, 213 | "content": " transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);", 214 | "highlight": true 215 | } 216 | ] 217 | }, 218 | { 219 | "functionName": "ReactDefaultBatchingStrategyTransaction.perform", 220 | "fileName": "http://localhost:3000/static/js/bundle.js", 221 | "lineNumber": 7324, 222 | "columnNumber": 20, 223 | "_originalFunctionName": "ReactDefaultBatchingStrategyTransaction.perform", 224 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/Transaction.js", 225 | "_originalLineNumber": 140, 226 | "_originalColumnNumber": 0, 227 | "_scriptCode": [ 228 | { 229 | "lineNumber": 7324, 230 | "content": " ret = method.call(scope, a, b, c, d, e, f);", 231 | "highlight": true 232 | } 233 | ], 234 | "_originalScriptCode": [ 235 | { 236 | "lineNumber": 140, 237 | "content": " ret = method.call(scope, a, b, c, d, e, f);", 238 | "highlight": true 239 | } 240 | ] 241 | }, 242 | { 243 | "functionName": "Object.batchedUpdates", 244 | "fileName": "http://localhost:3000/static/js/bundle.js", 245 | "lineNumber": 33900, 246 | "columnNumber": 26, 247 | "_originalFunctionName": "Object.batchedUpdates", 248 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactDefaultBatchingStrategy.js", 249 | "_originalLineNumber": 62, 250 | "_originalColumnNumber": 0, 251 | "_scriptCode": [ 252 | { 253 | "lineNumber": 33900, 254 | "content": " return transaction.perform(callback, null, a, b, c, d, e);", 255 | "highlight": true 256 | } 257 | ], 258 | "_originalScriptCode": [ 259 | { 260 | "lineNumber": 62, 261 | "content": " return transaction.perform(callback, null, a, b, c, d, e);", 262 | "highlight": true 263 | } 264 | ] 265 | }, 266 | { 267 | "functionName": "Object.batchedUpdates", 268 | "fileName": "http://localhost:3000/static/js/bundle.js", 269 | "lineNumber": 2181, 270 | "columnNumber": 27, 271 | "_originalFunctionName": "Object.batchedUpdates", 272 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactUpdates.js", 273 | "_originalLineNumber": 97, 274 | "_originalColumnNumber": 0, 275 | "_scriptCode": [ 276 | { 277 | "lineNumber": 2181, 278 | "content": " return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);", 279 | "highlight": true 280 | } 281 | ], 282 | "_originalScriptCode": [ 283 | { 284 | "lineNumber": 97, 285 | "content": " return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);", 286 | "highlight": true 287 | } 288 | ] 289 | }, 290 | { 291 | "functionName": "Object._renderNewRootComponent", 292 | "fileName": "http://localhost:3000/static/js/bundle.js", 293 | "lineNumber": 14398, 294 | "columnNumber": 18, 295 | "_originalFunctionName": "Object._renderNewRootComponent", 296 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", 297 | "_originalLineNumber": 320, 298 | "_originalColumnNumber": 0, 299 | "_scriptCode": [ 300 | { 301 | "lineNumber": 14398, 302 | "content": " ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);", 303 | "highlight": true 304 | } 305 | ], 306 | "_originalScriptCode": [ 307 | { 308 | "lineNumber": 320, 309 | "content": " ReactUpdates.batchedUpdates(batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context);", 310 | "highlight": true 311 | } 312 | ] 313 | }, 314 | { 315 | "functionName": "Object._renderSubtreeIntoContainer", 316 | "fileName": "http://localhost:3000/static/js/bundle.js", 317 | "lineNumber": 14479, 318 | "columnNumber": 32, 319 | "_originalFunctionName": "Object._renderSubtreeIntoContainer", 320 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", 321 | "_originalLineNumber": 401, 322 | "_originalColumnNumber": 0, 323 | "_scriptCode": [ 324 | { 325 | "lineNumber": 14479, 326 | "content": " var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();", 327 | "highlight": true 328 | } 329 | ], 330 | "_originalScriptCode": [ 331 | { 332 | "lineNumber": 401, 333 | "content": " var component = ReactMount._renderNewRootComponent(nextWrappedElement, container, shouldReuseMarkup, nextContext)._renderedComponent.getPublicInstance();", 334 | "highlight": true 335 | } 336 | ] 337 | }, 338 | { 339 | "functionName": "Object.render", 340 | "fileName": "http://localhost:3000/static/js/bundle.js", 341 | "lineNumber": 14500, 342 | "columnNumber": 23, 343 | "_originalFunctionName": "Object.render", 344 | "_originalFileName": "webpack:///packages/react-scripts/~/react-dom/lib/ReactMount.js", 345 | "_originalLineNumber": 422, 346 | "_originalColumnNumber": 0, 347 | "_scriptCode": [ 348 | { 349 | "lineNumber": 14500, 350 | "content": " return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);", 351 | "highlight": true 352 | } 353 | ], 354 | "_originalScriptCode": [ 355 | { 356 | "lineNumber": 422, 357 | "content": " return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);", 358 | "highlight": true 359 | } 360 | ] 361 | }, 362 | { 363 | "functionName": "Object.friendlySyntaxErrorLabel", 364 | "fileName": "http://localhost:3000/static/js/bundle.js", 365 | "lineNumber": 17287, 366 | "columnNumber": 20, 367 | "_originalFunctionName": "Object.friendlySyntaxErrorLabel", 368 | "_originalFileName": "webpack:///packages/react-scripts/template/src/index.js", 369 | "_originalLineNumber": 6, 370 | "_originalColumnNumber": 0, 371 | "_scriptCode": [ 372 | { 373 | "lineNumber": 17287, 374 | "content": "_reactDom2.default.render(_react2.default.createElement(_App2.default, {", 375 | "highlight": true 376 | } 377 | ], 378 | "_originalScriptCode": [ 379 | { 380 | "lineNumber": 6, 381 | "content": "ReactDOM.render(, document.getElementById('root'));", 382 | "highlight": true 383 | } 384 | ] 385 | }, 386 | { 387 | "functionName": "__webpack_require__", 388 | "fileName": "http://localhost:3000/static/js/bundle.js", 389 | "lineNumber": 660, 390 | "columnNumber": 30, 391 | "_originalFunctionName": "__webpack_require__", 392 | "_originalFileName": "webpack:/webpack/bootstrap 623dfc2b357036468bb6", 393 | "_originalLineNumber": 659, 394 | "_originalColumnNumber": 0, 395 | "_scriptCode": [ 396 | { 397 | "lineNumber": 660, 398 | "content": "/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));", 399 | "highlight": true 400 | } 401 | ], 402 | "_originalScriptCode": [ 403 | { 404 | "lineNumber": 659, 405 | "content": " \t\tmodules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));", 406 | "highlight": true 407 | } 408 | ] 409 | }, 410 | { 411 | "functionName": "fn", 412 | "fileName": "http://localhost:3000/static/js/bundle.js", 413 | "lineNumber": 84, 414 | "columnNumber": 20, 415 | "_originalFunctionName": "fn", 416 | "_originalFileName": "webpack:/webpack/bootstrap 623dfc2b357036468bb6", 417 | "_originalLineNumber": 83, 418 | "_originalColumnNumber": 0, 419 | "_scriptCode": [ 420 | { 421 | "lineNumber": 84, 422 | "content": "/******/ \t\t\treturn __webpack_require__(request);\r", 423 | "highlight": true 424 | } 425 | ], 426 | "_originalScriptCode": [ 427 | { 428 | "lineNumber": 83, 429 | "content": " \t\t\treturn __webpack_require__(request);\r", 430 | "highlight": true 431 | } 432 | ] 433 | }, 434 | { 435 | "functionName": "Object.", 436 | "fileName": "http://localhost:3000/static/js/bundle.js", 437 | "lineNumber": 41219, 438 | "columnNumber": 18, 439 | "_originalFunctionName": "Object.", 440 | "_originalFileName": null, 441 | "_originalLineNumber": null, 442 | "_originalColumnNumber": null, 443 | "_scriptCode": [ 444 | { 445 | "lineNumber": 41219, 446 | "content": "module.exports = __webpack_require__(/*! /Users/joe/Documents/Development/OSS/create-react-app/packages/react-scripts/template/src/index.js */130);", 447 | "highlight": true 448 | } 449 | ], 450 | "_originalScriptCode": [] 451 | }, 452 | { 453 | "functionName": "__webpack_require__", 454 | "fileName": "http://localhost:3000/static/js/bundle.js", 455 | "lineNumber": 660, 456 | "columnNumber": 30, 457 | "_originalFunctionName": "__webpack_require__", 458 | "_originalFileName": "webpack:/webpack/bootstrap 623dfc2b357036468bb6", 459 | "_originalLineNumber": 659, 460 | "_originalColumnNumber": 0, 461 | "_scriptCode": [ 462 | { 463 | "lineNumber": 660, 464 | "content": "/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));", 465 | "highlight": true 466 | } 467 | ], 468 | "_originalScriptCode": [ 469 | { 470 | "lineNumber": 659, 471 | "content": " \t\tmodules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));", 472 | "highlight": true 473 | } 474 | ] 475 | }, 476 | { 477 | "functionName": "validateFormat", 478 | "fileName": "http://localhost:3000/static/js/bundle.js", 479 | "lineNumber": 709, 480 | "columnNumber": 39, 481 | "_originalFunctionName": "validateFormat", 482 | "_originalFileName": "webpack:/webpack/bootstrap 623dfc2b357036468bb6", 483 | "_originalLineNumber": 708, 484 | "_originalColumnNumber": 0, 485 | "_scriptCode": [ 486 | { 487 | "lineNumber": 709, 488 | "content": "/******/ \treturn hotCreateRequire(302)(__webpack_require__.s = 302);", 489 | "highlight": true 490 | } 491 | ], 492 | "_originalScriptCode": [ 493 | { 494 | "lineNumber": 708, 495 | "content": " \treturn hotCreateRequire(302)(__webpack_require__.s = 302);", 496 | "highlight": true 497 | } 498 | ] 499 | }, 500 | { 501 | "functionName": null, 502 | "fileName": "http://localhost:3000/static/js/bundle.js", 503 | "lineNumber": 712, 504 | "columnNumber": 10, 505 | "_originalFunctionName": null, 506 | "_originalFileName": null, 507 | "_originalLineNumber": null, 508 | "_originalColumnNumber": null, 509 | "_scriptCode": [ 510 | { 511 | "lineNumber": 712, 512 | "content": "/******/ ([", 513 | "highlight": true 514 | } 515 | ], 516 | "_originalScriptCode": [] 517 | } 518 | ] 519 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-frame-mapper", 3 | "version": "0.4.0", 4 | "description": "Maps a stack frame to a sourcemap.", 5 | "scripts": { 6 | "prepublishOnly": "npm run build && npm test", 7 | "build": "../../node_modules/.bin/babel src/ -d lib/", 8 | "test": "../../node_modules/.bin/jest" 9 | }, 10 | "main": "lib/index.js", 11 | "repository": "https://github.com/Timer/stack-frame/tree/master/packages/stack-frame-mapper", 12 | "author": "Joe Haddad ", 13 | "license": "MIT", 14 | "files": [ 15 | "lib/" 16 | ], 17 | "dependencies": { 18 | "babel-runtime": "^6.23.0", 19 | "settle-promise": "^1.0.0", 20 | "stack-frame": "0.4.0", 21 | "stack-frame-utils": "0.4.0" 22 | }, 23 | "devDependencies": { 24 | "stack-frame-parser": "0.4.0" 25 | }, 26 | "jest": { 27 | "setupFiles": [ 28 | "./src/__tests__/setupJest.js" 29 | ], 30 | "collectCoverage": true, 31 | "coverageReporters": [ 32 | "json" 33 | ], 34 | "testMatch": [ 35 | "/src/**/__tests__/**/*.js?(x)", 36 | "/src/**/?(*.)(spec|test).js?(x)" 37 | ], 38 | "testPathIgnorePatterns": [ 39 | "/node_modules/", 40 | "/fixtures/", 41 | "setupJest.js" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/src/__tests__/bundle.js: -------------------------------------------------------------------------------- 1 | import { map } from '../'; 2 | import { parse } from 'stack-frame-parser'; 3 | import fs from 'fs'; 4 | import { resolve } from 'path'; 5 | 6 | test('basic error; 0 context', async () => { 7 | expect.assertions(1); 8 | const error = 9 | 'TypeError: document.body.missing is not a function\n at App.componentDidMount (http://localhost:3000/static/js/bundle.js:26122:21)\n at http://localhost:3000/static/js/bundle.js:30091:25\n at measureLifeCyclePerf (http://localhost:3000/static/js/bundle.js:29901:12)\n at http://localhost:3000/static/js/bundle.js:30090:11\n at CallbackQueue.notifyAll (http://localhost:3000/static/js/bundle.js:13256:22)\n at ReactReconcileTransaction.close (http://localhost:3000/static/js/bundle.js:35124:26)\n at ReactReconcileTransaction.closeAll (http://localhost:3000/static/js/bundle.js:7390:25)\n at ReactReconcileTransaction.perform (http://localhost:3000/static/js/bundle.js:7337:16)\n at batchedMountComponentIntoNode (http://localhost:3000/static/js/bundle.js:14204:15)\n at ReactDefaultBatchingStrategyTransaction.perform (http://localhost:3000/static/js/bundle.js:7324:20)\n at Object.batchedUpdates (http://localhost:3000/static/js/bundle.js:33900:26)\n at Object.batchedUpdates (http://localhost:3000/static/js/bundle.js:2181:27)\n at Object._renderNewRootComponent (http://localhost:3000/static/js/bundle.js:14398:18)\n at Object._renderSubtreeIntoContainer (http://localhost:3000/static/js/bundle.js:14479:32)\n at Object.render (http://localhost:3000/static/js/bundle.js:14500:23)\n at Object.friendlySyntaxErrorLabel (http://localhost:3000/static/js/bundle.js:17287:20)\n at __webpack_require__ (http://localhost:3000/static/js/bundle.js:660:30)\n at fn (http://localhost:3000/static/js/bundle.js:84:20)\n at Object. (http://localhost:3000/static/js/bundle.js:41219:18)\n at __webpack_require__ (http://localhost:3000/static/js/bundle.js:660:30)\n at validateFormat (http://localhost:3000/static/js/bundle.js:709:39)\n at http://localhost:3000/static/js/bundle.js:712:10'; 10 | 11 | fetch.mockResponseOnce( 12 | fs 13 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js')) 14 | .toString('utf8') 15 | ); 16 | fetch.mockResponseOnce( 17 | fs 18 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js.map')) 19 | .toString('utf8') 20 | ); 21 | const frames = await map(parse(error), 0); 22 | expect(frames).toEqual( 23 | JSON.parse( 24 | fs 25 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.json')) 26 | .toString('utf8') 27 | ) 28 | ); 29 | }); 30 | 31 | test('default context (3)', async () => { 32 | expect.assertions(1); 33 | const error = 34 | 'TypeError: document.body.missing is not a function\n at App.componentDidMount (http://localhost:3000/static/js/bundle.js:26122:21)'; 35 | 36 | fetch.mockResponseOnce( 37 | fs 38 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js')) 39 | .toString('utf8') 40 | ); 41 | fetch.mockResponseOnce( 42 | fs 43 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js.map')) 44 | .toString('utf8') 45 | ); 46 | const frames = await map(parse(error)); 47 | expect(frames).toEqual( 48 | JSON.parse( 49 | fs 50 | .readFileSync(resolve(__dirname, '../../fixtures/bundle-default.json')) 51 | .toString('utf8') 52 | ) 53 | ); 54 | }); 55 | 56 | test('bad comes back same', async () => { 57 | expect.assertions(2); 58 | const error = 59 | 'TypeError: document.body.missing is not a function\n at App.componentDidMount (A:1:2)'; 60 | const orig = parse(error); 61 | expect(orig).toEqual([ 62 | { 63 | _originalColumnNumber: null, 64 | _originalFileName: null, 65 | _originalFunctionName: null, 66 | _originalLineNumber: null, 67 | _originalScriptCode: null, 68 | _scriptCode: null, 69 | columnNumber: 2, 70 | fileName: 'A', 71 | functionName: 'App.componentDidMount', 72 | lineNumber: 1, 73 | }, 74 | ]); 75 | const frames = await map(orig); 76 | expect(frames).toEqual(orig); 77 | }); 78 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/src/__tests__/setupJest.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | global.fetch = require('jest-fetch-mock'); 3 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/src/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import StackFrame from 'stack-frame'; 3 | import { getSourceMap, getLinesAround } from 'stack-frame-utils'; 4 | import { settle } from 'settle-promise'; 5 | 6 | /** 7 | * Enhances a set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s with their original positions and code (when available). 8 | * @param {StackFrame[]} frames A set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s which contain (generated) code positions. 9 | * @param {number} [contextLines=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. 10 | */ 11 | async function map( 12 | frames: StackFrame[], 13 | contextLines: number = 3 14 | ): Promise { 15 | const cache = {}; 16 | const files = []; 17 | frames.forEach(frame => { 18 | const { fileName } = frame; 19 | if (files.indexOf(fileName) !== -1) { 20 | return; 21 | } 22 | files.push(fileName); 23 | }); 24 | await settle( 25 | files.map(async fileName => { 26 | const fileSource = await fetch(fileName).then(r => r.text()); 27 | const map = await getSourceMap(fileName, fileSource); 28 | cache[fileName] = { fileSource, map }; 29 | }) 30 | ); 31 | return frames.map(frame => { 32 | const { functionName, fileName, lineNumber, columnNumber } = frame; 33 | let { map, fileSource } = cache[fileName] || {}; 34 | if (map == null) { 35 | return frame; 36 | } 37 | const { source, line, column } = map.getOriginalPosition( 38 | lineNumber, 39 | columnNumber 40 | ); 41 | const originalSource = source == null ? [] : map.getSource(source); 42 | return new StackFrame( 43 | functionName, 44 | fileName, 45 | lineNumber, 46 | columnNumber, 47 | getLinesAround(lineNumber, contextLines, fileSource), 48 | functionName, 49 | source, 50 | line, 51 | column, 52 | getLinesAround(line, contextLines, originalSource) 53 | ); 54 | }); 55 | } 56 | 57 | export { map }; 58 | export default map; 59 | -------------------------------------------------------------------------------- /packages/stack-frame-mapper/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | babel-runtime@^6.23.0: 6 | version "6.23.0" 7 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" 8 | dependencies: 9 | core-js "^2.4.0" 10 | regenerator-runtime "^0.10.0" 11 | 12 | core-js@^2.4.0: 13 | version "2.4.1" 14 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" 15 | 16 | regenerator-runtime@^0.10.0: 17 | version "0.10.3" 18 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e" 19 | 20 | settle-promise@^1.0.0: 21 | version "1.0.0" 22 | resolved "https://registry.yarnpkg.com/settle-promise/-/settle-promise-1.0.0.tgz#697adb58b821f387ce2757c06efc9de5f0ee33d8" 23 | 24 | source-map@^0.5.6: 25 | version "0.5.6" 26 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 27 | 28 | stack-frame-parser@0.4.0: 29 | version "0.4.0" 30 | resolved "https://registry.yarnpkg.com/stack-frame-parser/-/stack-frame-parser-0.4.0.tgz#9b7f856fa6a9ef86e5d8cd3b11e33a48f52c1bb6" 31 | dependencies: 32 | stack-frame "0.4.0" 33 | 34 | stack-frame-utils@0.4.0: 35 | version "0.4.0" 36 | resolved "https://registry.yarnpkg.com/stack-frame-utils/-/stack-frame-utils-0.4.0.tgz#3ecd38765690c1e2ee7127057cc11ac3b7038d36" 37 | dependencies: 38 | babel-runtime "^6.23.0" 39 | source-map "^0.5.6" 40 | 41 | stack-frame@0.4.0: 42 | version "0.4.0" 43 | resolved "https://registry.yarnpkg.com/stack-frame/-/stack-frame-0.4.0.tgz#147100cbb78bd69a0a0a318ea2c0af8406ffbb95" 44 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/README.md: -------------------------------------------------------------------------------- 1 | # `stack-frame-overlay` 2 | 3 | An overlay for displaying stack frames. 4 | 5 | # API 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-frame-overlay", 3 | "version": "0.4.0", 4 | "description": "An overlay for displaying stack frames.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "prepublishOnly": "npm run build && npm test", 8 | "build": "../../node_modules/.bin/babel src/ -d lib/", 9 | "test": "../../node_modules/.bin/jest" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Timer/stack-frame/tree/master/packages/stack-frame-overlay" 14 | }, 15 | "keywords": [ 16 | "overlay", 17 | "syntax", 18 | "error", 19 | "red", 20 | "box", 21 | "redbox", 22 | "crash", 23 | "warning" 24 | ], 25 | "author": "Joe Haddad ", 26 | "license": "MIT", 27 | "files": [ 28 | "lib/" 29 | ], 30 | "dependencies": { 31 | "anser": "^1.2.5", 32 | "ansi-html-themed": "^0.1.0", 33 | "babel-code-frame": "^6.22.0", 34 | "stack-frame": "0.4.0", 35 | "stack-frame-mapper": "0.4.0", 36 | "stack-frame-parser": "0.4.0", 37 | "stack-frame-unmapper": "0.4.0" 38 | }, 39 | "devDependencies": { 40 | "rollup": "^0.41.6" 41 | }, 42 | "jest": { 43 | "setupFiles": [ 44 | "./src/__tests__/setupJest.js" 45 | ], 46 | "collectCoverage": true, 47 | "coverageReporters": [ 48 | "json" 49 | ], 50 | "testMatch": [ 51 | "/src/**/__tests__/**/*.js?(x)", 52 | "/src/**/?(*.)(spec|test).js?(x)" 53 | ], 54 | "testPathIgnorePatterns": [ 55 | "/node_modules/", 56 | "/fixtures/", 57 | "setupJest.js" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/__tests__/mount.js: -------------------------------------------------------------------------------- 1 | import '..'; 2 | 3 | test('is registered', () => {}); 4 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/__tests__/setupJest.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timer/stack-frame/19468d6b013d296c5aacd618a02ee9486feae544/packages/stack-frame-overlay/src/__tests__/setupJest.js -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/additional.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { applyStyles } from '../utils/dom/css'; 3 | import { groupStyle, groupElemLeft, groupElemRight } from '../styles'; 4 | import { consumeEvent } from '../utils/dom/consumeEvent'; 5 | import { enableTabClick } from '../utils/dom/enableTabClick'; 6 | 7 | type SwitchCallback = (offset: number) => void; 8 | function updateAdditional( 9 | document: Document, 10 | additionalReference: HTMLDivElement, 11 | currentError: number, 12 | totalErrors: number, 13 | switchCallback: SwitchCallback 14 | ) { 15 | if (additionalReference.lastChild) { 16 | additionalReference.removeChild(additionalReference.lastChild); 17 | } 18 | 19 | let text = ' '; 20 | if (totalErrors <= 1) { 21 | additionalReference.appendChild(document.createTextNode(text)); 22 | return; 23 | } 24 | text = `Errors ${currentError} of ${totalErrors}`; 25 | const span = document.createElement('span'); 26 | span.appendChild(document.createTextNode(text)); 27 | const group = document.createElement('span'); 28 | applyStyles(group, groupStyle); 29 | const left = document.createElement('button'); 30 | applyStyles(left, groupElemLeft); 31 | left.addEventListener('click', function(e: MouseEvent) { 32 | consumeEvent(e); 33 | switchCallback(-1); 34 | }); 35 | left.appendChild(document.createTextNode('←')); 36 | enableTabClick(left); 37 | const right = document.createElement('button'); 38 | applyStyles(right, groupElemRight); 39 | right.addEventListener('click', function(e: MouseEvent) { 40 | consumeEvent(e); 41 | switchCallback(1); 42 | }); 43 | right.appendChild(document.createTextNode('→')); 44 | enableTabClick(right); 45 | group.appendChild(left); 46 | group.appendChild(right); 47 | span.appendChild(group); 48 | additionalReference.appendChild(span); 49 | } 50 | 51 | export type { SwitchCallback }; 52 | export { updateAdditional }; 53 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/close.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { applyStyles } from '../utils/dom/css'; 3 | import { hintsStyle, hintStyle, closeButtonStyle } from '../styles'; 4 | 5 | function createHint(document: Document, hint: string) { 6 | const span = document.createElement('span'); 7 | span.appendChild(document.createTextNode(hint)); 8 | applyStyles(span, hintStyle); 9 | return span; 10 | } 11 | 12 | type CloseCallback = () => void; 13 | function createClose(document: Document, callback: CloseCallback) { 14 | const hints = document.createElement('div'); 15 | applyStyles(hints, hintsStyle); 16 | 17 | const close = createHint(document, '×'); 18 | close.addEventListener('click', () => callback()); 19 | applyStyles(close, closeButtonStyle); 20 | hints.appendChild(close); 21 | return hints; 22 | } 23 | 24 | export type { CloseCallback }; 25 | export { createClose }; 26 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/code.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { ScriptLines } from 'stack-frame'; 3 | import { applyStyles } from '../utils/dom/css'; 4 | import { absolutifyCaret } from '../utils/dom/absolutifyCaret'; 5 | import { 6 | preStyle, 7 | codeStyle, 8 | primaryErrorStyle, 9 | secondaryErrorStyle, 10 | } from '../styles'; 11 | 12 | import codeFrame from 'babel-code-frame'; 13 | import { generateAnsiHtml } from 'ansi-html-themed'; 14 | 15 | function createCode( 16 | document: Document, 17 | sourceLines: ScriptLines[], 18 | lineNum: number, 19 | columnNum: number, 20 | contextSize: number, 21 | main: boolean = false 22 | ) { 23 | const sourceCode = []; 24 | let whiteSpace = Infinity; 25 | sourceLines.forEach(function(e) { 26 | const { content: text } = e; 27 | const m = text.match(/^\s*/); 28 | if (text === '') { 29 | return; 30 | } 31 | if (m && m[0]) { 32 | whiteSpace = Math.min(whiteSpace, m[0].length); 33 | } else { 34 | whiteSpace = 0; 35 | } 36 | }); 37 | sourceLines.forEach(function(e) { 38 | let { content: text } = e; 39 | const { lineNumber: line } = e; 40 | 41 | if (isFinite(whiteSpace)) { 42 | text = text.substring(whiteSpace); 43 | } 44 | sourceCode[line - 1] = text; 45 | }); 46 | const ansiHighlight = codeFrame( 47 | sourceCode.join('\n'), 48 | lineNum, 49 | columnNum - (isFinite(whiteSpace) ? whiteSpace : 0), 50 | { 51 | forceColor: true, 52 | linesAbove: contextSize, 53 | linesBelow: contextSize, 54 | } 55 | ); 56 | const htmlHighlight = generateAnsiHtml(ansiHighlight); 57 | const code = document.createElement('code'); 58 | code.innerHTML = htmlHighlight; 59 | absolutifyCaret(code); 60 | applyStyles(code, codeStyle); 61 | 62 | const ccn = code.childNodes; 63 | oLoop: for (let index = 0; index < ccn.length; ++index) { 64 | const node = ccn[index]; 65 | const ccn2 = node.childNodes; 66 | for (let index2 = 0; index2 < ccn2.length; ++index2) { 67 | const lineNode = ccn2[index2]; 68 | const text = lineNode.innerText; 69 | if (text == null) { 70 | continue; 71 | } 72 | if (text.indexOf(' ' + lineNum + ' |') === -1) { 73 | continue; 74 | } 75 | // $FlowFixMe 76 | applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle); 77 | break oLoop; 78 | } 79 | } 80 | const pre = document.createElement('pre'); 81 | applyStyles(pre, preStyle); 82 | pre.appendChild(code); 83 | return pre; 84 | } 85 | 86 | export { createCode }; 87 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/footer.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { applyStyles } from '../utils/dom/css'; 3 | import { footerStyle } from '../styles'; 4 | 5 | function createFooter(document: Document) { 6 | const div = document.createElement('div'); 7 | applyStyles(div, footerStyle); 8 | div.appendChild( 9 | document.createTextNode( 10 | 'This screen is visible only in development. It will not appear when the app crashes in production.' 11 | ) 12 | ); 13 | div.appendChild(document.createElement('br')); 14 | div.appendChild( 15 | document.createTextNode( 16 | 'Open your browser’s developer console to further inspect this error.' 17 | ) 18 | ); 19 | return div; 20 | } 21 | 22 | export { createFooter }; 23 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/frame.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { enableTabClick } from '../utils/dom/enableTabClick'; 3 | import { createCode } from './code'; 4 | import { isInternalFile } from '../utils/isInternalFile'; 5 | import type { StackFrame } from 'stack-frame'; 6 | import type { FrameSetting, OmitsObject } from './frames'; 7 | import { applyStyles } from '../utils/dom/css'; 8 | import { 9 | omittedFramesStyle, 10 | functionNameStyle, 11 | depStyle, 12 | linkStyle, 13 | anchorStyle, 14 | hiddenStyle, 15 | } from '../styles'; 16 | 17 | function getGroupToggle( 18 | document: Document, 19 | omitsCount: number, 20 | omitBundle: number 21 | ) { 22 | const omittedFrames = document.createElement('div'); 23 | enableTabClick(omittedFrames); 24 | const text1 = document.createTextNode( 25 | '\u25B6 ' + omitsCount + ' stack frames were collapsed.' 26 | ); 27 | omittedFrames.appendChild(text1); 28 | omittedFrames.addEventListener('click', function() { 29 | const hide = text1.textContent.match(/▲/); 30 | const list = document.getElementsByName('bundle-' + omitBundle); 31 | for (let index = 0; index < list.length; ++index) { 32 | const n = list[index]; 33 | if (hide) { 34 | n.style.display = 'none'; 35 | } else { 36 | n.style.display = ''; 37 | } 38 | } 39 | if (hide) { 40 | text1.textContent = text1.textContent.replace(/▲/, '▶'); 41 | text1.textContent = text1.textContent.replace(/expanded/, 'collapsed'); 42 | } else { 43 | text1.textContent = text1.textContent.replace(/▶/, '▲'); 44 | text1.textContent = text1.textContent.replace(/collapsed/, 'expanded'); 45 | } 46 | }); 47 | applyStyles(omittedFrames, omittedFramesStyle); 48 | return omittedFrames; 49 | } 50 | 51 | function insertBeforeBundle( 52 | document: Document, 53 | parent: Node, 54 | omitsCount: number, 55 | omitBundle: number, 56 | actionElement 57 | ) { 58 | const children = document.getElementsByName('bundle-' + omitBundle); 59 | if (children.length < 1) { 60 | return; 61 | } 62 | let first: ?Node = children[0]; 63 | while (first != null && first.parentNode !== parent) { 64 | first = first.parentNode; 65 | } 66 | const div = document.createElement('div'); 67 | enableTabClick(div); 68 | div.setAttribute('name', 'bundle-' + omitBundle); 69 | const text = document.createTextNode( 70 | '\u25BC ' + omitsCount + ' stack frames were expanded.' 71 | ); 72 | div.appendChild(text); 73 | div.addEventListener('click', function() { 74 | return actionElement.click(); 75 | }); 76 | applyStyles(div, omittedFramesStyle); 77 | div.style.display = 'none'; 78 | 79 | parent.insertBefore(div, first); 80 | } 81 | 82 | function frameDiv(document: Document, functionName, url, internalUrl) { 83 | const frame = document.createElement('div'); 84 | const frameFunctionName = document.createElement('div'); 85 | 86 | let cleanedFunctionName; 87 | if (!functionName || functionName === 'Object.') { 88 | cleanedFunctionName = '(anonymous function)'; 89 | } else { 90 | cleanedFunctionName = functionName; 91 | } 92 | 93 | const cleanedUrl = url.replace('webpack://', '.'); 94 | 95 | if (internalUrl) { 96 | applyStyles( 97 | frameFunctionName, 98 | Object.assign({}, functionNameStyle, depStyle) 99 | ); 100 | } else { 101 | applyStyles(frameFunctionName, functionNameStyle); 102 | } 103 | 104 | frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName)); 105 | frame.appendChild(frameFunctionName); 106 | 107 | const frameLink = document.createElement('div'); 108 | applyStyles(frameLink, linkStyle); 109 | const frameAnchor = document.createElement('a'); 110 | applyStyles(frameAnchor, anchorStyle); 111 | frameAnchor.appendChild(document.createTextNode(cleanedUrl)); 112 | frameLink.appendChild(frameAnchor); 113 | frame.appendChild(frameLink); 114 | 115 | return frame; 116 | } 117 | 118 | function createFrame( 119 | document: Document, 120 | frameSetting: FrameSetting, 121 | frame: StackFrame, 122 | contextSize: number, 123 | critical: boolean, 124 | omits: OmitsObject, 125 | omitBundle: number, 126 | parentContainer: HTMLDivElement, 127 | lastElement: boolean 128 | ) { 129 | const { compiled } = frameSetting; 130 | const { 131 | functionName, 132 | fileName, 133 | lineNumber, 134 | columnNumber, 135 | _scriptCode: scriptLines, 136 | _originalFileName: sourceFileName, 137 | _originalLineNumber: sourceLineNumber, 138 | _originalColumnNumber: sourceColumnNumber, 139 | _originalScriptCode: sourceLines, 140 | } = frame; 141 | 142 | let url; 143 | if (!compiled && sourceFileName) { 144 | url = sourceFileName + ':' + sourceLineNumber; 145 | if (sourceColumnNumber) { 146 | url += ':' + sourceColumnNumber; 147 | } 148 | } else { 149 | url = fileName + ':' + lineNumber; 150 | if (columnNumber) { 151 | url += ':' + columnNumber; 152 | } 153 | } 154 | 155 | let needsHidden = false; 156 | const internalUrl = isInternalFile(url, sourceFileName); 157 | if (internalUrl) { 158 | ++omits.value; 159 | needsHidden = true; 160 | } 161 | let collapseElement = null; 162 | if (!internalUrl || lastElement) { 163 | if (omits.value > 0) { 164 | const capV = omits.value; 165 | const omittedFrames = getGroupToggle(document, capV, omitBundle); 166 | window.requestAnimationFrame(() => { 167 | insertBeforeBundle( 168 | document, 169 | parentContainer, 170 | capV, 171 | omitBundle, 172 | omittedFrames 173 | ); 174 | }); 175 | if (lastElement && internalUrl) { 176 | collapseElement = omittedFrames; 177 | } else { 178 | parentContainer.appendChild(omittedFrames); 179 | } 180 | ++omits.bundle; 181 | } 182 | omits.value = 0; 183 | } 184 | 185 | const elem = frameDiv(document, functionName, url, internalUrl); 186 | if (needsHidden) { 187 | applyStyles(elem, hiddenStyle); 188 | elem.setAttribute('name', 'bundle-' + omitBundle); 189 | } 190 | 191 | let hasSource = false; 192 | if (!internalUrl) { 193 | if (compiled && scriptLines.length !== 0) { 194 | elem.appendChild( 195 | createCode( 196 | document, 197 | scriptLines, 198 | lineNumber, 199 | columnNumber, 200 | contextSize, 201 | critical 202 | ) 203 | ); 204 | hasSource = true; 205 | } else if (!compiled && sourceLines.length !== 0) { 206 | elem.appendChild( 207 | createCode( 208 | document, 209 | sourceLines, 210 | sourceLineNumber, 211 | sourceColumnNumber, 212 | contextSize, 213 | critical 214 | ) 215 | ); 216 | hasSource = true; 217 | } 218 | } 219 | 220 | return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; 221 | } 222 | 223 | export { createFrame }; 224 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/frames.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { StackFrame } from 'stack-frame'; 3 | import { applyStyles } from '../utils/dom/css'; 4 | import { traceStyle, toggleStyle } from '../styles'; 5 | import { enableTabClick } from '../utils/dom/enableTabClick'; 6 | import { createFrame } from './frame'; 7 | 8 | type OmitsObject = { value: number, bundle: number }; 9 | type FrameSetting = { compiled: boolean }; 10 | export type { OmitsObject, FrameSetting }; 11 | 12 | function createFrameWrapper( 13 | document: Document, 14 | parent: HTMLDivElement, 15 | factory, 16 | lIndex: number, 17 | frameSettings: FrameSetting[], 18 | contextSize: number 19 | ) { 20 | const fac = factory(); 21 | if (fac == null) { 22 | return; 23 | } 24 | const { hasSource, elem, collapseElement } = fac; 25 | 26 | const elemWrapper = document.createElement('div'); 27 | elemWrapper.appendChild(elem); 28 | 29 | if (hasSource) { 30 | const compiledDiv = document.createElement('div'); 31 | enableTabClick(compiledDiv); 32 | applyStyles(compiledDiv, toggleStyle); 33 | 34 | const o = frameSettings[lIndex]; 35 | const compiledText = document.createTextNode( 36 | 'View ' + (o && o.compiled ? 'source' : 'compiled') 37 | ); 38 | compiledDiv.addEventListener('click', function() { 39 | if (o) { 40 | o.compiled = !o.compiled; 41 | } 42 | 43 | const next = createFrameWrapper( 44 | document, 45 | parent, 46 | factory, 47 | lIndex, 48 | frameSettings, 49 | contextSize 50 | ); 51 | if (next != null) { 52 | parent.insertBefore(next, elemWrapper); 53 | parent.removeChild(elemWrapper); 54 | } 55 | }); 56 | compiledDiv.appendChild(compiledText); 57 | elemWrapper.appendChild(compiledDiv); 58 | } 59 | 60 | if (collapseElement != null) { 61 | elemWrapper.appendChild(collapseElement); 62 | } 63 | 64 | return elemWrapper; 65 | } 66 | 67 | function createFrames( 68 | document: Document, 69 | resolvedFrames: StackFrame[], 70 | frameSettings: FrameSetting[], 71 | contextSize: number 72 | ) { 73 | if (resolvedFrames.length !== frameSettings.length) { 74 | throw new Error( 75 | 'You must give a frame settings array of identical length to resolved frames.' 76 | ); 77 | } 78 | const trace = document.createElement('div'); 79 | applyStyles(trace, traceStyle); 80 | 81 | let index = 0; 82 | let critical = true; 83 | const omits: OmitsObject = { value: 0, bundle: 1 }; 84 | resolvedFrames.forEach(function(frame) { 85 | const lIndex = index++; 86 | const elem = createFrameWrapper( 87 | document, 88 | trace, 89 | createFrame.bind( 90 | undefined, 91 | document, 92 | frameSettings[lIndex], 93 | frame, 94 | contextSize, 95 | critical, 96 | omits, 97 | omits.bundle, 98 | trace, 99 | index === resolvedFrames.length 100 | ), 101 | lIndex, 102 | frameSettings, 103 | contextSize 104 | ); 105 | if (elem == null) { 106 | return; 107 | } 108 | critical = false; 109 | trace.appendChild(elem); 110 | }); 111 | //TODO: fix this 112 | omits.value = 0; 113 | 114 | return trace; 115 | } 116 | 117 | export { createFrames }; 118 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/components/overlay.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { applyStyles } from '../utils/dom/css'; 3 | import { overlayStyle, headerStyle, additionalStyle } from '../styles'; 4 | import { createClose } from './close'; 5 | import { createFrames } from './frames'; 6 | import { createFooter } from './footer'; 7 | import type { CloseCallback } from './close'; 8 | import type { StackFrame } from 'stack-frame'; 9 | import { updateAdditional } from './additional'; 10 | import type { FrameSetting } from './frames'; 11 | import type { SwitchCallback } from './additional'; 12 | 13 | function createOverlay( 14 | document: Document, 15 | name: string, 16 | message: string, 17 | frames: StackFrame[], 18 | contextSize: number, 19 | currentError: number, 20 | totalErrors: number, 21 | switchCallback: SwitchCallback, 22 | closeCallback: CloseCallback 23 | ): { 24 | overlay: HTMLDivElement, 25 | additional: HTMLDivElement, 26 | } { 27 | const frameSettings: FrameSetting[] = frames.map(() => ({ compiled: false })); 28 | // Create overlay 29 | const overlay = document.createElement('div'); 30 | applyStyles(overlay, overlayStyle); 31 | overlay.appendChild(createClose(document, closeCallback)); 32 | 33 | // Create container 34 | const container = document.createElement('div'); 35 | container.className = 'cra-container'; 36 | overlay.appendChild(container); 37 | 38 | // Create additional 39 | const additional = document.createElement('div'); 40 | applyStyles(additional, additionalStyle); 41 | container.appendChild(additional); 42 | updateAdditional( 43 | document, 44 | additional, 45 | currentError, 46 | totalErrors, 47 | switchCallback 48 | ); 49 | 50 | // Create header 51 | const header = document.createElement('div'); 52 | applyStyles(header, headerStyle); 53 | if (message.match(/^\w*:/)) { 54 | header.appendChild(document.createTextNode(message)); 55 | } else { 56 | header.appendChild(document.createTextNode(name + ': ' + message)); 57 | } 58 | container.appendChild(header); 59 | 60 | // Create trace 61 | container.appendChild( 62 | createFrames(document, frames, frameSettings, contextSize) 63 | ); 64 | 65 | // Show message 66 | container.appendChild(createFooter(document)); 67 | 68 | return { 69 | overlay, 70 | additional, 71 | }; 72 | } 73 | 74 | export { createOverlay }; 75 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/effects/proxyConsole.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | type ConsoleProxyCallback = (message: string) => void; 3 | const permanentRegister = function proxyConsole( 4 | type: string, 5 | callback: ConsoleProxyCallback 6 | ) { 7 | const orig = console[type]; 8 | console[type] = function __stack_frame_overlay_proxy_console__() { 9 | const message = [].slice.call(arguments).join(' '); 10 | callback(message); 11 | return orig.apply(this, arguments); 12 | }; 13 | }; 14 | 15 | export { permanentRegister }; 16 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/effects/shortcuts.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | const SHORTCUT_ESCAPE = 'SHORTCUT_ESCAPE', 3 | SHORTCUT_LEFT = 'SHORTCUT_LEFT', 4 | SHORTCUT_RIGHT = 'SHORTCUT_RIGHT'; 5 | 6 | let boundKeyHandler = null; 7 | 8 | type ShortcutCallback = (type: string) => void; 9 | 10 | function keyHandler(callback: ShortcutCallback, e: KeyboardEvent) { 11 | const { key, keyCode, which } = e; 12 | if (key === 'Escape' || keyCode === 27 || which === 27) { 13 | callback(SHORTCUT_ESCAPE); 14 | } else if (key === 'ArrowLeft' || keyCode === 37 || which === 37) { 15 | callback(SHORTCUT_LEFT); 16 | } else if (key === 'ArrowRight' || keyCode === 39 || which === 39) { 17 | callback(SHORTCUT_RIGHT); 18 | } 19 | } 20 | 21 | function registerShortcuts(target: EventTarget, callback: ShortcutCallback) { 22 | if (boundKeyHandler !== null) { 23 | return; 24 | } 25 | boundKeyHandler = keyHandler.bind(undefined, callback); 26 | target.addEventListener('keydown', boundKeyHandler); 27 | } 28 | 29 | function unregisterShortcuts(target: EventTarget) { 30 | if (boundKeyHandler === null) { 31 | return; 32 | } 33 | target.removeEventListener('keydown', boundKeyHandler); 34 | boundKeyHandler = null; 35 | } 36 | 37 | export { 38 | SHORTCUT_ESCAPE, 39 | SHORTCUT_LEFT, 40 | SHORTCUT_RIGHT, 41 | registerShortcuts as register, 42 | unregisterShortcuts as unregister, 43 | keyHandler as handler, 44 | }; 45 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/effects/stackTraceLimit.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | let stackTraceRegistered: boolean = false; 3 | // Default: https://docs.microsoft.com/en-us/scripting/javascript/reference/stacktracelimit-property-error-javascript 4 | let restoreStackTraceValue: number = 10; 5 | 6 | const MAX_STACK_LENGTH: number = 50; 7 | 8 | function registerStackTraceLimit(limit: number = MAX_STACK_LENGTH) { 9 | if (stackTraceRegistered) { 10 | return; 11 | } 12 | try { 13 | restoreStackTraceValue = Error.stackTraceLimit; 14 | Error.stackTraceLimit = limit; 15 | stackTraceRegistered = true; 16 | } catch (e) { 17 | // Not all browsers support this so we don't care if it errors 18 | } 19 | } 20 | 21 | function unregisterStackTraceLimit() { 22 | if (!stackTraceRegistered) { 23 | return; 24 | } 25 | try { 26 | Error.stackTraceLimit = restoreStackTraceValue; 27 | stackTraceRegistered = false; 28 | } catch (e) { 29 | // Not all browsers support this so we don't care if it errors 30 | } 31 | } 32 | 33 | export { 34 | registerStackTraceLimit as register, 35 | unregisterStackTraceLimit as unregister, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/effects/unhandledError.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | let boundErrorHandler = null; 3 | 4 | type ErrorCallback = (error: Error) => void; 5 | 6 | function errorHandler(callback: ErrorCallback, e: Event): void { 7 | if (!e.error) { 8 | return; 9 | } 10 | // $FlowFixMe 11 | const { error } = e; 12 | if (error instanceof Error) { 13 | callback(error); 14 | } else { 15 | // A non-error was thrown, we don't have a trace. :( 16 | // Look in your browser's devtools for more information 17 | callback(new Error(error)); 18 | } 19 | } 20 | 21 | function registerUnhandledError(target: EventTarget, callback: ErrorCallback) { 22 | if (boundErrorHandler !== null) { 23 | return; 24 | } 25 | boundErrorHandler = errorHandler.bind(undefined, callback); 26 | target.addEventListener('error', boundErrorHandler); 27 | } 28 | 29 | function unregisterUnhandledError(target: EventTarget) { 30 | if (boundErrorHandler === null) { 31 | return; 32 | } 33 | target.removeEventListener('error', boundErrorHandler); 34 | boundErrorHandler = null; 35 | } 36 | 37 | export { 38 | registerUnhandledError as register, 39 | unregisterUnhandledError as unregister, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/effects/unhandledRejection.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | let boundRejectionHandler = null; 3 | 4 | type ErrorCallback = (error: Error) => void; 5 | 6 | function rejectionHandler( 7 | callback: ErrorCallback, 8 | e: PromiseRejectionEvent 9 | ): void { 10 | if (e == null || e.reason == null) { 11 | return callback(new Error('Unknown')); 12 | } 13 | let { reason } = e; 14 | if (reason instanceof Error) { 15 | return callback(reason); 16 | } 17 | // A non-error was rejected, we don't have a trace :( 18 | // Look in your browser's devtools for more information 19 | return callback(new Error(reason)); 20 | } 21 | 22 | function registerUnhandledRejection( 23 | target: EventTarget, 24 | callback: ErrorCallback 25 | ) { 26 | if (boundRejectionHandler !== null) { 27 | return; 28 | } 29 | boundRejectionHandler = rejectionHandler.bind(undefined, callback); 30 | // $FlowFixMe 31 | target.addEventListener('unhandledrejection', boundRejectionHandler); 32 | } 33 | 34 | function unregisterUnhandledRejection(target: EventTarget) { 35 | if (boundRejectionHandler === null) { 36 | return; 37 | } 38 | // $FlowFixMe 39 | target.removeEventListener('unhandledrejection', boundRejectionHandler); 40 | boundRejectionHandler = null; 41 | } 42 | 43 | export { 44 | registerUnhandledRejection as register, 45 | unregisterUnhandledRejection as unregister, 46 | }; 47 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { inject, uninject } from './overlay'; 3 | 4 | inject(); 5 | if (module.hot && typeof module.hot.dispose === 'function') { 6 | module.hot.dispose(function() { 7 | uninject(); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/overlay.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { 3 | register as registerError, 4 | unregister as unregisterError, 5 | } from './effects/unhandledError'; 6 | import { 7 | register as registerPromise, 8 | unregister as unregisterPromise, 9 | } from './effects/unhandledRejection'; 10 | import { 11 | register as registerShortcuts, 12 | unregister as unregisterShortcuts, 13 | handler as keyEventHandler, 14 | SHORTCUT_ESCAPE, 15 | SHORTCUT_LEFT, 16 | SHORTCUT_RIGHT, 17 | } from './effects/shortcuts'; 18 | import { 19 | register as registerStackTraceLimit, 20 | unregister as unregisterStackTraceLimit, 21 | } from './effects/stackTraceLimit'; 22 | 23 | import { 24 | consume as consumeError, 25 | getErrorRecord, 26 | drain as drainErrors, 27 | } from './utils/errorRegister'; 28 | import type { ErrorRecordReference } from './utils/errorRegister'; 29 | 30 | import type { StackFrame } from 'stack-frame'; 31 | import { iframeStyle } from './styles'; 32 | import { injectCss, applyStyles } from './utils/dom/css'; 33 | import { createOverlay } from './components/overlay'; 34 | import { updateAdditional } from './components/additional'; 35 | 36 | const CONTEXT_SIZE: number = 3; 37 | let iframeReference: HTMLIFrameElement | null = null; 38 | let additionalReference = null; 39 | let errorReferences: ErrorRecordReference[] = []; 40 | let currReferenceIndex: number = -1; 41 | 42 | const css = [ 43 | '.cra-container {', 44 | ' padding-right: 15px;', 45 | ' padding-left: 15px;', 46 | ' margin-right: auto;', 47 | ' margin-left: auto;', 48 | '}', 49 | '', 50 | '@media (min-width: 768px) {', 51 | ' .cra-container {', 52 | ' width: calc(750px - 6em);', 53 | ' }', 54 | '}', 55 | '', 56 | '@media (min-width: 992px) {', 57 | ' .cra-container {', 58 | ' width: calc(970px - 6em);', 59 | ' }', 60 | '}', 61 | '', 62 | '@media (min-width: 1200px) {', 63 | ' .cra-container {', 64 | ' width: calc(1170px - 6em);', 65 | ' }', 66 | '}', 67 | ].join('\n'); 68 | 69 | function render(name: string, message: string, resolvedFrames: StackFrame[]) { 70 | disposeCurrentView(); 71 | 72 | const iframe = window.document.createElement('iframe'); 73 | applyStyles(iframe, iframeStyle); 74 | iframeReference = iframe; 75 | iframe.onload = () => { 76 | if (iframeReference == null) { 77 | return; 78 | } 79 | const w = iframeReference.contentWindow; 80 | const document = iframeReference.contentDocument; 81 | 82 | const { overlay, additional } = createOverlay( 83 | document, 84 | name, 85 | message, 86 | resolvedFrames, 87 | CONTEXT_SIZE, 88 | currReferenceIndex + 1, 89 | errorReferences.length, 90 | offset => { 91 | switchError(offset); 92 | }, 93 | () => { 94 | unmount(); 95 | } 96 | ); 97 | if (w != null) { 98 | w.onkeydown = event => { 99 | keyEventHandler(type => shortcutHandler(type), event); 100 | }; 101 | } 102 | injectCss(iframeReference.contentDocument, css); 103 | if (document.body != null) { 104 | document.body.appendChild(overlay); 105 | } 106 | additionalReference = additional; 107 | }; 108 | window.document.body.appendChild(iframe); 109 | } 110 | 111 | function renderErrorByIndex(index: number) { 112 | currReferenceIndex = index; 113 | 114 | const { error, unhandledRejection, enhancedFrames } = getErrorRecord( 115 | errorReferences[index] 116 | ); 117 | 118 | if (unhandledRejection) { 119 | render( 120 | 'Unhandled Rejection (' + error.name + ')', 121 | error.message, 122 | enhancedFrames 123 | ); 124 | } else { 125 | render(error.name, error.message, enhancedFrames); 126 | } 127 | } 128 | 129 | function switchError(offset) { 130 | const nextView = currReferenceIndex + offset; 131 | if (nextView < 0 || nextView >= errorReferences.length) { 132 | return; 133 | } 134 | renderErrorByIndex(nextView); 135 | } 136 | 137 | function disposeCurrentView() { 138 | if (iframeReference === null) { 139 | return; 140 | } 141 | window.document.body.removeChild(iframeReference); 142 | iframeReference = null; 143 | additionalReference = null; 144 | } 145 | 146 | function unmount() { 147 | disposeCurrentView(); 148 | drainErrors(); 149 | errorReferences = []; 150 | currReferenceIndex = -1; 151 | } 152 | 153 | function crash(error: Error, unhandledRejection = false) { 154 | if (module.hot && typeof module.hot.decline === 'function') { 155 | module.hot.decline(); 156 | } 157 | consumeError(error, unhandledRejection, CONTEXT_SIZE) 158 | .then(ref => { 159 | errorReferences.push(ref); 160 | if (iframeReference !== null && additionalReference !== null) { 161 | updateAdditional( 162 | iframeReference.contentDocument, 163 | additionalReference, 164 | currReferenceIndex + 1, 165 | errorReferences.length, 166 | offset => { 167 | switchError(offset); 168 | } 169 | ); 170 | } else { 171 | if (errorReferences.length !== 1) { 172 | throw new Error('Something is *really* wrong.'); 173 | } 174 | renderErrorByIndex((currReferenceIndex = 0)); 175 | } 176 | }) 177 | .catch(e => { 178 | console.log('Could not consume error:', e); 179 | }); 180 | } 181 | 182 | function shortcutHandler(type: string) { 183 | switch (type) { 184 | case SHORTCUT_ESCAPE: { 185 | unmount(); 186 | break; 187 | } 188 | case SHORTCUT_LEFT: { 189 | switchError(-1); 190 | break; 191 | } 192 | case SHORTCUT_RIGHT: { 193 | switchError(1); 194 | break; 195 | } 196 | } 197 | } 198 | 199 | function inject() { 200 | registerError(window, error => crash(error)); 201 | registerPromise(window, error => crash(error, true)); 202 | registerShortcuts(window, shortcutHandler); 203 | registerStackTraceLimit(); 204 | } 205 | 206 | function uninject() { 207 | unregisterStackTraceLimit(); 208 | unregisterShortcuts(window); 209 | unregisterPromise(window); 210 | unregisterError(window); 211 | } 212 | 213 | export { inject, uninject }; 214 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/styles.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | const black = '#293238', 3 | darkGray = '#878e91', 4 | lightGray = '#fafafa', 5 | red = '#ce1126', 6 | lightRed = '#fccfcf', 7 | yellow = '#fbf5b4'; 8 | 9 | const iframeStyle = { 10 | 'background-color': lightGray, 11 | position: 'fixed', 12 | top: '1em', 13 | left: '1em', 14 | bottom: '1em', 15 | right: '1em', 16 | width: 'calc(100% - 2em)', 17 | height: 'calc(100% - 2em)', 18 | border: 'none', 19 | 'border-radius': '3px', 20 | 'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)', 21 | 'z-index': 1337, 22 | }; 23 | 24 | const overlayStyle = { 25 | 'box-sizing': 'border-box', 26 | padding: '4rem', 27 | 'font-family': 'Consolas, Menlo, monospace', 28 | color: black, 29 | 'white-space': 'pre-wrap', 30 | overflow: 'auto', 31 | 'overflow-x': 'hidden', 32 | 'word-break': 'break-word', 33 | 'line-height': 1.5, 34 | }; 35 | 36 | const hintsStyle = { 37 | 'font-size': '0.8em', 38 | 'margin-top': '-3em', 39 | 'margin-bottom': '3em', 40 | 'text-align': 'right', 41 | color: darkGray, 42 | }; 43 | 44 | const hintStyle = { 45 | padding: '0.5em 1em', 46 | cursor: 'pointer', 47 | }; 48 | 49 | const closeButtonStyle = { 50 | 'font-size': '26px', 51 | color: black, 52 | padding: '0.5em 1em', 53 | cursor: 'pointer', 54 | position: 'absolute', 55 | right: 0, 56 | top: 0, 57 | }; 58 | 59 | const additionalStyle = { 60 | 'margin-bottom': '1.5em', 61 | 'margin-top': '-4em', 62 | }; 63 | 64 | const headerStyle = { 65 | 'font-size': '1.7em', 66 | 'font-weight': 'bold', 67 | color: red, 68 | }; 69 | 70 | const functionNameStyle = { 71 | 'margin-top': '1em', 72 | 'font-size': '1.2em', 73 | }; 74 | 75 | const linkStyle = { 76 | 'font-size': '0.9em', 77 | }; 78 | 79 | const anchorStyle = { 80 | 'text-decoration': 'none', 81 | color: darkGray, 82 | }; 83 | 84 | const traceStyle = { 85 | 'font-size': '1em', 86 | }; 87 | 88 | const depStyle = { 89 | 'font-size': '1.2em', 90 | }; 91 | 92 | const primaryErrorStyle = { 93 | 'background-color': lightRed, 94 | }; 95 | 96 | const secondaryErrorStyle = { 97 | 'background-color': yellow, 98 | }; 99 | 100 | const omittedFramesStyle = { 101 | color: black, 102 | 'font-size': '0.9em', 103 | margin: '1.5em 0', 104 | cursor: 'pointer', 105 | }; 106 | 107 | const preStyle = { 108 | display: 'block', 109 | padding: '0.5em', 110 | 'margin-top': '1.5em', 111 | 'margin-bottom': '0px', 112 | 'overflow-x': 'auto', 113 | 'font-size': '1.1em', 114 | 'white-space': 'pre', 115 | }; 116 | 117 | const toggleStyle = { 118 | 'margin-bottom': '1.5em', 119 | color: darkGray, 120 | cursor: 'pointer', 121 | }; 122 | 123 | const codeStyle = { 124 | 'font-family': 'Consolas, Menlo, monospace', 125 | }; 126 | 127 | const hiddenStyle = { 128 | display: 'none', 129 | }; 130 | 131 | const groupStyle = { 132 | 'margin-left': '1em', 133 | }; 134 | 135 | const _groupElemStyle = { 136 | 'background-color': 'inherit', 137 | 'border-color': '#ddd', 138 | 'border-width': '1px', 139 | 'border-radius': '4px', 140 | 'border-style': 'solid', 141 | padding: '3px 6px', 142 | cursor: 'pointer', 143 | }; 144 | 145 | const groupElemLeft = Object.assign({}, _groupElemStyle, { 146 | 'border-top-right-radius': '0px', 147 | 'border-bottom-right-radius': '0px', 148 | 'margin-right': '0px', 149 | }); 150 | 151 | const groupElemRight = Object.assign({}, _groupElemStyle, { 152 | 'border-top-left-radius': '0px', 153 | 'border-bottom-left-radius': '0px', 154 | 'margin-left': '-1px', 155 | }); 156 | 157 | const footerStyle = { 158 | 'text-align': 'center', 159 | color: darkGray, 160 | }; 161 | 162 | export { 163 | iframeStyle, 164 | overlayStyle, 165 | hintsStyle, 166 | hintStyle, 167 | closeButtonStyle, 168 | additionalStyle, 169 | headerStyle, 170 | functionNameStyle, 171 | linkStyle, 172 | anchorStyle, 173 | traceStyle, 174 | depStyle, 175 | primaryErrorStyle, 176 | secondaryErrorStyle, 177 | omittedFramesStyle, 178 | preStyle, 179 | toggleStyle, 180 | codeStyle, 181 | hiddenStyle, 182 | groupStyle, 183 | groupElemLeft, 184 | groupElemRight, 185 | footerStyle, 186 | }; 187 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/utils/dom/absolutifyCaret.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | function removeNextBr(parent, component: ?Element) { 3 | while (component != null && component.tagName.toLowerCase() !== 'br') { 4 | component = component.nextElementSibling; 5 | } 6 | if (component != null) { 7 | parent.removeChild(component); 8 | } 9 | } 10 | 11 | function absolutifyCaret(component: Node) { 12 | const ccn = component.childNodes; 13 | for (let index = 0; index < ccn.length; ++index) { 14 | const c = ccn[index]; 15 | // $FlowFixMe 16 | if (c.tagName.toLowerCase() !== 'span') { 17 | continue; 18 | } 19 | const _text = c.innerText; 20 | if (_text == null) { 21 | continue; 22 | } 23 | const text = _text.replace(/\s/g, ''); 24 | if (text !== '|^') { 25 | continue; 26 | } 27 | // $FlowFixMe 28 | c.style.position = 'absolute'; 29 | // $FlowFixMe 30 | removeNextBr(component, c); 31 | } 32 | } 33 | 34 | export { absolutifyCaret }; 35 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/utils/dom/consumeEvent.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | function consumeEvent(e: Event) { 3 | e.preventDefault(); 4 | if (typeof e.target.blur === 'function') { 5 | e.target.blur(); 6 | } 7 | } 8 | 9 | export { consumeEvent }; 10 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/utils/dom/css.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | let injectedCount = 0; 3 | const injectedCache = {}; 4 | 5 | function getHead(document: Document) { 6 | return document.head || document.getElementsByTagName('head')[0]; 7 | } 8 | 9 | function injectCss(document: Document, css: string): number { 10 | const head = getHead(document); 11 | const style = document.createElement('style'); 12 | style.type = 'text/css'; 13 | style.appendChild(document.createTextNode(css)); 14 | head.appendChild(style); 15 | 16 | injectedCache[++injectedCount] = style; 17 | return injectedCount; 18 | } 19 | 20 | function removeCss(document: Document, ref: number) { 21 | if (injectedCache[ref] == null) { 22 | return; 23 | } 24 | const head = getHead(document); 25 | head.removeChild(injectedCache[ref]); 26 | delete injectedCache[ref]; 27 | } 28 | 29 | function applyStyles(element: HTMLElement, styles: Object) { 30 | element.setAttribute('style', ''); 31 | for (const key in styles) { 32 | if (!styles.hasOwnProperty(key)) { 33 | continue; 34 | } 35 | // $FlowFixMe 36 | element.style[key] = styles[key]; 37 | } 38 | } 39 | 40 | export { getHead, injectCss, removeCss, applyStyles }; 41 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/utils/dom/enableTabClick.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | function enableTabClick(node: Element) { 3 | node.setAttribute('tabindex', '0'); 4 | node.addEventListener('keydown', function(e: KeyboardEvent) { 5 | const { key, which, keyCode } = e; 6 | if (key === 'Enter' || which === 13 || keyCode === 13) { 7 | e.preventDefault(); 8 | if (typeof e.target.click === 'function') { 9 | e.target.click(); 10 | } 11 | } 12 | }); 13 | } 14 | 15 | export { enableTabClick }; 16 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/utils/errorRegister.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import type { StackFrame } from 'stack-frame'; 3 | import { parse } from 'stack-frame-parser'; 4 | import { map } from 'stack-frame-mapper'; 5 | import { unmap } from 'stack-frame-unmapper'; 6 | 7 | type ErrorRecord = { 8 | error: Error, 9 | unhandledRejection: boolean, 10 | contextSize: number, 11 | enhancedFrames: StackFrame[], 12 | }; 13 | type ErrorRecordReference = number; 14 | const recorded: ErrorRecord[] = []; 15 | 16 | let errorsConsumed: ErrorRecordReference = 0; 17 | 18 | function consume( 19 | error: Error, 20 | unhandledRejection: boolean = false, 21 | contextSize: number = 3 22 | ): Promise { 23 | const parsedFrames = parse(error); 24 | let enhancedFramesPromise; 25 | if (error.__unmap_source) { 26 | enhancedFramesPromise = unmap( 27 | error.__unmap_source, 28 | parsedFrames, 29 | contextSize 30 | ); 31 | } else { 32 | enhancedFramesPromise = map(parsedFrames, contextSize); 33 | } 34 | return enhancedFramesPromise.then(enhancedFrames => { 35 | enhancedFrames = enhancedFrames.filter( 36 | ({ functionName }) => 37 | functionName == null || 38 | functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1 39 | ); 40 | recorded[++errorsConsumed] = { 41 | error, 42 | unhandledRejection, 43 | contextSize, 44 | enhancedFrames, 45 | }; 46 | return errorsConsumed; 47 | }); 48 | } 49 | 50 | function getErrorRecord(ref: ErrorRecordReference): ErrorRecord { 51 | return recorded[ref]; 52 | } 53 | 54 | function drain() { 55 | // $FlowFixMe 56 | const keys = Object.keys(recorded); 57 | for (let index = 0; index < keys.length; ++index) { 58 | delete recorded[keys[index]]; 59 | } 60 | } 61 | 62 | export { consume, getErrorRecord, drain }; 63 | export type { ErrorRecordReference }; 64 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/src/utils/isInternalFile.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | function isInternalFile(url: string, sourceFileName: string | null | void) { 3 | return ( 4 | url.indexOf('/~/') !== -1 || 5 | url.indexOf('/node_modules/') !== -1 || 6 | url.trim().indexOf(' ') !== -1 || 7 | sourceFileName == null || 8 | sourceFileName.length === 0 9 | ); 10 | } 11 | 12 | export { isInternalFile }; 13 | -------------------------------------------------------------------------------- /packages/stack-frame-overlay/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | anser@^1.2.5: 6 | version "1.2.7" 7 | resolved "https://registry.yarnpkg.com/anser/-/anser-1.2.7.tgz#fb1a41a05ffdef5b9e33d5d64794765fd76524e0" 8 | 9 | ansi-html-themed@^0.1.0: 10 | version "0.1.0" 11 | resolved "https://registry.yarnpkg.com/ansi-html-themed/-/ansi-html-themed-0.1.0.tgz#571235b4c7034475239c7457610e63d25d5a02fa" 12 | dependencies: 13 | anser "^1.2.5" 14 | 15 | ansi-regex@^2.0.0: 16 | version "2.1.1" 17 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 18 | 19 | ansi-styles@^2.2.1: 20 | version "2.2.1" 21 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 22 | 23 | babel-code-frame@^6.22.0: 24 | version "6.22.0" 25 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" 26 | dependencies: 27 | chalk "^1.1.0" 28 | esutils "^2.0.2" 29 | js-tokens "^3.0.0" 30 | 31 | babel-runtime@^6.23.0: 32 | version "6.23.0" 33 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" 34 | dependencies: 35 | core-js "^2.4.0" 36 | regenerator-runtime "^0.10.0" 37 | 38 | chalk@^1.1.0: 39 | version "1.1.3" 40 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 41 | dependencies: 42 | ansi-styles "^2.2.1" 43 | escape-string-regexp "^1.0.2" 44 | has-ansi "^2.0.0" 45 | strip-ansi "^3.0.0" 46 | supports-color "^2.0.0" 47 | 48 | core-js@^2.4.0: 49 | version "2.4.1" 50 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" 51 | 52 | escape-string-regexp@^1.0.2: 53 | version "1.0.5" 54 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 55 | 56 | esutils@^2.0.2: 57 | version "2.0.2" 58 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 59 | 60 | has-ansi@^2.0.0: 61 | version "2.0.0" 62 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 63 | dependencies: 64 | ansi-regex "^2.0.0" 65 | 66 | js-tokens@^3.0.0: 67 | version "3.0.1" 68 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" 69 | 70 | regenerator-runtime@^0.10.0: 71 | version "0.10.3" 72 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e" 73 | 74 | rollup@^0.41.6: 75 | version "0.41.6" 76 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.41.6.tgz#e0d05497877a398c104d816d2733a718a7a94e2a" 77 | dependencies: 78 | source-map-support "^0.4.0" 79 | 80 | settle-promise@^1.0.0: 81 | version "1.0.0" 82 | resolved "https://registry.yarnpkg.com/settle-promise/-/settle-promise-1.0.0.tgz#697adb58b821f387ce2757c06efc9de5f0ee33d8" 83 | 84 | source-map-support@^0.4.0: 85 | version "0.4.14" 86 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.14.tgz#9d4463772598b86271b4f523f6c1f4e02a7d6aef" 87 | dependencies: 88 | source-map "^0.5.6" 89 | 90 | source-map@^0.5.6: 91 | version "0.5.6" 92 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 93 | 94 | stack-frame-mapper@0.4.0: 95 | version "0.4.0" 96 | resolved "https://registry.yarnpkg.com/stack-frame-mapper/-/stack-frame-mapper-0.4.0.tgz#ee885b32c84166f5913aecfa34fd77fe7c7c5e01" 97 | dependencies: 98 | babel-runtime "^6.23.0" 99 | settle-promise "^1.0.0" 100 | stack-frame "0.4.0" 101 | stack-frame-utils "0.4.0" 102 | 103 | stack-frame-parser@0.4.0: 104 | version "0.4.0" 105 | resolved "https://registry.yarnpkg.com/stack-frame-parser/-/stack-frame-parser-0.4.0.tgz#9b7f856fa6a9ef86e5d8cd3b11e33a48f52c1bb6" 106 | dependencies: 107 | stack-frame "0.4.0" 108 | 109 | stack-frame-unmapper@0.4.0: 110 | version "0.4.0" 111 | resolved "https://registry.yarnpkg.com/stack-frame-unmapper/-/stack-frame-unmapper-0.4.0.tgz#90fed8ae4e853307e84f7d1a0638a9b4265b6098" 112 | dependencies: 113 | babel-runtime "^6.23.0" 114 | stack-frame "0.4.0" 115 | stack-frame-utils "0.4.0" 116 | 117 | stack-frame-utils@0.4.0: 118 | version "0.4.0" 119 | resolved "https://registry.yarnpkg.com/stack-frame-utils/-/stack-frame-utils-0.4.0.tgz#3ecd38765690c1e2ee7127057cc11ac3b7038d36" 120 | dependencies: 121 | babel-runtime "^6.23.0" 122 | source-map "^0.5.6" 123 | 124 | stack-frame@0.4.0: 125 | version "0.4.0" 126 | resolved "https://registry.yarnpkg.com/stack-frame/-/stack-frame-0.4.0.tgz#147100cbb78bd69a0a0a318ea2c0af8406ffbb95" 127 | 128 | strip-ansi@^3.0.0: 129 | version "3.0.1" 130 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 131 | dependencies: 132 | ansi-regex "^2.0.0" 133 | 134 | supports-color@^2.0.0: 135 | version "2.0.0" 136 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 137 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/README.md: -------------------------------------------------------------------------------- 1 | # `stack-frame-parser` 2 | 3 | Parses a stack trace into stack frames. 4 | 5 | # API 6 | 7 | 8 | 9 | ## parse 10 | 11 | Turns an Error, or similar object, into a set of [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe)s. 12 | 13 | **Parameters** 14 | 15 | - `error` **([Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) \| [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>)** 16 | 17 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<StackFrame>** 18 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-frame-parser", 3 | "version": "0.4.0", 4 | "description": "Parses a stack trace into stack frames.", 5 | "scripts": { 6 | "prepublishOnly": "npm run build && npm test", 7 | "build": "../../node_modules/.bin/babel src/ -d lib/", 8 | "test": "../../node_modules/.bin/jest" 9 | }, 10 | "main": "lib/index.js", 11 | "repository": "https://github.com/Timer/stack-frame/tree/master/packages/stack-frame-parser", 12 | "author": "Joe Haddad ", 13 | "license": "MIT", 14 | "files": [ 15 | "lib/" 16 | ], 17 | "dependencies": { 18 | "stack-frame": "0.4.0" 19 | }, 20 | "devDependencies": { 21 | "chalk": "^1.1.3" 22 | }, 23 | "jest": { 24 | "collectCoverage": true, 25 | "coverageThreshold": { 26 | "global": { 27 | "statements": 100 28 | } 29 | }, 30 | "coverageReporters": [ 31 | "json" 32 | ], 33 | "testMatch": [ 34 | "/src/**/__tests__/**/*.js?(x)", 35 | "/src/**/?(*.)(spec|test).js?(x)" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/src/__tests__/chrome.js: -------------------------------------------------------------------------------- 1 | import { parse } from '../'; 2 | 3 | test('stack with eval', () => { 4 | expect( 5 | parse( 6 | `TypeError: window[f] is not a function 7 | at e (file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:25:18) 8 | at eval (eval at c (file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:12:9), :1:1) 9 | at a (file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:8:9) 10 | at file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:32:7` 11 | ) 12 | ).toEqual([ 13 | { 14 | functionName: 'e', 15 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 16 | lineNumber: 25, 17 | columnNumber: 18, 18 | _originalColumnNumber: null, 19 | _originalFileName: null, 20 | _originalFunctionName: null, 21 | _originalLineNumber: null, 22 | _originalScriptCode: null, 23 | _scriptCode: null, 24 | }, 25 | { 26 | functionName: 'eval', 27 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 28 | lineNumber: 12, 29 | columnNumber: 9, 30 | _originalColumnNumber: null, 31 | _originalFileName: null, 32 | _originalFunctionName: null, 33 | _originalLineNumber: null, 34 | _originalScriptCode: null, 35 | _scriptCode: null, 36 | }, 37 | { 38 | functionName: 'a', 39 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 40 | lineNumber: 8, 41 | columnNumber: 9, 42 | _originalColumnNumber: null, 43 | _originalFileName: null, 44 | _originalFunctionName: null, 45 | _originalLineNumber: null, 46 | _originalScriptCode: null, 47 | _scriptCode: null, 48 | }, 49 | { 50 | functionName: null, 51 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 52 | lineNumber: 32, 53 | columnNumber: 7, 54 | _originalColumnNumber: null, 55 | _originalFileName: null, 56 | _originalFunctionName: null, 57 | _originalLineNumber: null, 58 | _originalScriptCode: null, 59 | _scriptCode: null, 60 | }, 61 | ]); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/src/__tests__/firefox.js: -------------------------------------------------------------------------------- 1 | import { parse } from '../'; 2 | 3 | test('eval 1', () => { 4 | expect( 5 | parse( 6 | `test1@file:///C:/example.html line 7 > eval line 1 > eval:1:1 7 | test2@file:///C:/example.html line 7 > eval:1:1 8 | test3@file:///C:/example.html:7:6`.split('\n') 9 | ) 10 | ).toEqual([ 11 | { 12 | fileName: 'file:///C:/example.html', 13 | functionName: 'test1', 14 | lineNumber: 7, 15 | columnNumber: null, 16 | _originalColumnNumber: null, 17 | _originalFileName: null, 18 | _originalFunctionName: null, 19 | _originalLineNumber: null, 20 | _originalScriptCode: null, 21 | _scriptCode: null, 22 | }, 23 | { 24 | fileName: 'file:///C:/example.html', 25 | functionName: 'test2', 26 | lineNumber: 7, 27 | columnNumber: null, 28 | _originalColumnNumber: null, 29 | _originalFileName: null, 30 | _originalFunctionName: null, 31 | _originalLineNumber: null, 32 | _originalScriptCode: null, 33 | _scriptCode: null, 34 | }, 35 | { 36 | functionName: 'test3', 37 | fileName: 'file:///C:/example.html', 38 | lineNumber: 7, 39 | columnNumber: 6, 40 | _originalColumnNumber: null, 41 | _originalFileName: null, 42 | _originalFunctionName: null, 43 | _originalLineNumber: null, 44 | _originalScriptCode: null, 45 | _scriptCode: null, 46 | }, 47 | ]); 48 | }); 49 | 50 | test('eval 2', () => { 51 | expect( 52 | parse({ 53 | stack: `anonymous@file:///C:/example.html line 7 > Function:1:1 54 | @file:///C:/example.html:7:6`, 55 | }) 56 | ).toEqual([ 57 | { 58 | fileName: 'file:///C:/example.html', 59 | functionName: 'anonymous', 60 | lineNumber: 7, 61 | columnNumber: null, 62 | _originalColumnNumber: null, 63 | _originalFileName: null, 64 | _originalFunctionName: null, 65 | _originalLineNumber: null, 66 | _originalScriptCode: null, 67 | _scriptCode: null, 68 | }, 69 | { 70 | fileName: 'file:///C:/example.html', 71 | functionName: null, 72 | lineNumber: 7, 73 | columnNumber: 6, 74 | _originalColumnNumber: null, 75 | _originalFileName: null, 76 | _originalFunctionName: null, 77 | _originalLineNumber: null, 78 | _originalScriptCode: null, 79 | _scriptCode: null, 80 | }, 81 | ]); 82 | }); 83 | 84 | test('stack with eval', () => { 85 | expect( 86 | parse( 87 | `e@file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:25:9 88 | @file:///Users/joe/Documents/Development/OSS/stack-frame/index.html line 17 > eval:1:1 89 | a@file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:8:9 90 | @file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:32:7` 91 | ) 92 | ).toEqual([ 93 | { 94 | functionName: 'e', 95 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 96 | columnNumber: 9, 97 | lineNumber: 25, 98 | _originalColumnNumber: null, 99 | _originalFileName: null, 100 | _originalFunctionName: null, 101 | _originalLineNumber: null, 102 | _originalScriptCode: null, 103 | _scriptCode: null, 104 | }, 105 | { 106 | functionName: 'eval', 107 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 108 | lineNumber: 17, 109 | columnNumber: null, 110 | _originalColumnNumber: null, 111 | _originalFileName: null, 112 | _originalFunctionName: null, 113 | _originalLineNumber: null, 114 | _originalScriptCode: null, 115 | _scriptCode: null, 116 | }, 117 | { 118 | functionName: 'a', 119 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 120 | columnNumber: 9, 121 | lineNumber: 8, 122 | _originalColumnNumber: null, 123 | _originalFileName: null, 124 | _originalFunctionName: null, 125 | _originalLineNumber: null, 126 | _originalScriptCode: null, 127 | _scriptCode: null, 128 | }, 129 | { 130 | functionName: null, 131 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 132 | columnNumber: 7, 133 | lineNumber: 32, 134 | _originalColumnNumber: null, 135 | _originalFileName: null, 136 | _originalFunctionName: null, 137 | _originalLineNumber: null, 138 | _originalScriptCode: null, 139 | _scriptCode: null, 140 | }, 141 | ]); 142 | }); 143 | 144 | test('v14 to v29', () => { 145 | expect( 146 | parse( 147 | `trace@file:///C:/example.html:9 148 | b@file:///C:/example.html:16 149 | a@file:///C:/example.html:19 150 | @file:///C:/example.html:21` 151 | ) 152 | ).toEqual([ 153 | { 154 | functionName: 'trace', 155 | lineNumber: 9, 156 | columnNumber: null, 157 | fileName: 'file:///C:/example.html', 158 | _originalColumnNumber: null, 159 | _originalFileName: null, 160 | _originalFunctionName: null, 161 | _originalLineNumber: null, 162 | _originalScriptCode: null, 163 | _scriptCode: null, 164 | }, 165 | { 166 | functionName: 'b', 167 | lineNumber: 16, 168 | columnNumber: null, 169 | fileName: 'file:///C:/example.html', 170 | _originalColumnNumber: null, 171 | _originalFileName: null, 172 | _originalFunctionName: null, 173 | _originalLineNumber: null, 174 | _originalScriptCode: null, 175 | _scriptCode: null, 176 | }, 177 | { 178 | functionName: 'a', 179 | lineNumber: 19, 180 | columnNumber: null, 181 | fileName: 'file:///C:/example.html', 182 | _originalColumnNumber: null, 183 | _originalFileName: null, 184 | _originalFunctionName: null, 185 | _originalLineNumber: null, 186 | _originalScriptCode: null, 187 | _scriptCode: null, 188 | }, 189 | { 190 | functionName: null, 191 | lineNumber: 21, 192 | columnNumber: null, 193 | fileName: 'file:///C:/example.html', 194 | _originalColumnNumber: null, 195 | _originalFileName: null, 196 | _originalFunctionName: null, 197 | _originalLineNumber: null, 198 | _originalScriptCode: null, 199 | _scriptCode: null, 200 | }, 201 | ]); 202 | }); 203 | 204 | test('v30+', () => { 205 | expect( 206 | parse( 207 | `trace@file:///C:/example.html:9:17 208 | b@file:///C:/example.html:16:13 209 | a@file:///C:/example.html:19:13 210 | @file:///C:/example.html:21:9` 211 | ) 212 | ).toEqual([ 213 | { 214 | functionName: 'trace', 215 | lineNumber: 9, 216 | columnNumber: 17, 217 | fileName: 'file:///C:/example.html', 218 | _originalColumnNumber: null, 219 | _originalFileName: null, 220 | _originalFunctionName: null, 221 | _originalLineNumber: null, 222 | _originalScriptCode: null, 223 | _scriptCode: null, 224 | }, 225 | { 226 | functionName: 'b', 227 | lineNumber: 16, 228 | columnNumber: 13, 229 | fileName: 'file:///C:/example.html', 230 | _originalColumnNumber: null, 231 | _originalFileName: null, 232 | _originalFunctionName: null, 233 | _originalLineNumber: null, 234 | _originalScriptCode: null, 235 | _scriptCode: null, 236 | }, 237 | { 238 | functionName: 'a', 239 | lineNumber: 19, 240 | columnNumber: 13, 241 | fileName: 'file:///C:/example.html', 242 | _originalColumnNumber: null, 243 | _originalFileName: null, 244 | _originalFunctionName: null, 245 | _originalLineNumber: null, 246 | _originalScriptCode: null, 247 | _scriptCode: null, 248 | }, 249 | { 250 | functionName: null, 251 | lineNumber: 21, 252 | columnNumber: 9, 253 | fileName: 'file:///C:/example.html', 254 | _originalColumnNumber: null, 255 | _originalFileName: null, 256 | _originalFunctionName: null, 257 | _originalLineNumber: null, 258 | _originalScriptCode: null, 259 | _scriptCode: null, 260 | }, 261 | ]); 262 | }); 263 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/src/__tests__/generic.js: -------------------------------------------------------------------------------- 1 | import { parse } from '../'; 2 | 3 | test('throws on null', () => { 4 | expect.assertions(2); 5 | try { 6 | parse(null); 7 | } catch (e) { 8 | expect(e instanceof Error).toBe(true); 9 | expect(e.message).toBe('You cannot pass a null object.'); 10 | } 11 | }); 12 | 13 | test('throws on unparsable', () => { 14 | expect.assertions(2); 15 | try { 16 | parse({}); 17 | } catch (e) { 18 | expect(e instanceof Error).toBe(true); 19 | expect(e.message).toBe( 20 | 'The error you provided does not contain a stack trace.' 21 | ); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/src/__tests__/react.js: -------------------------------------------------------------------------------- 1 | import { parse } from '../'; 2 | 3 | test('15.y.z', () => { 4 | expect( 5 | parse( 6 | `Warning: Each child in array should have a unique "key" prop. Check render method of \`FileA\`. 7 | in div (at FileA.js:9) 8 | in FileA (at App.js:9) 9 | in div (at App.js:8) 10 | in App (at index.js:7)` 11 | ) 12 | ).toEqual([ 13 | { 14 | functionName: 'div', 15 | fileName: 'FileA.js', 16 | lineNumber: 9, 17 | columnNumber: null, 18 | _originalColumnNumber: null, 19 | _originalFileName: null, 20 | _originalFunctionName: null, 21 | _originalLineNumber: null, 22 | _originalScriptCode: null, 23 | _scriptCode: null, 24 | }, 25 | { 26 | functionName: 'FileA', 27 | fileName: 'App.js', 28 | lineNumber: 9, 29 | columnNumber: null, 30 | _originalColumnNumber: null, 31 | _originalFileName: null, 32 | _originalFunctionName: null, 33 | _originalLineNumber: null, 34 | _originalScriptCode: null, 35 | _scriptCode: null, 36 | }, 37 | { 38 | functionName: 'div', 39 | fileName: 'App.js', 40 | lineNumber: 8, 41 | columnNumber: null, 42 | _originalColumnNumber: null, 43 | _originalFileName: null, 44 | _originalFunctionName: null, 45 | _originalLineNumber: null, 46 | _originalScriptCode: null, 47 | _scriptCode: null, 48 | }, 49 | { 50 | functionName: 'App', 51 | fileName: 'index.js', 52 | lineNumber: 7, 53 | columnNumber: null, 54 | _originalColumnNumber: null, 55 | _originalFileName: null, 56 | _originalFunctionName: null, 57 | _originalLineNumber: null, 58 | _originalScriptCode: null, 59 | _scriptCode: null, 60 | }, 61 | ]); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/src/__tests__/safari.js: -------------------------------------------------------------------------------- 1 | import { parse } from '../'; 2 | 3 | test('stack with eval', () => { 4 | expect( 5 | parse( 6 | `e@file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:25:18 7 | eval code 8 | eval@[native code] 9 | a@file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:8:10 10 | global code@file:///Users/joe/Documents/Development/OSS/stack-frame/index.html:32:8` 11 | ) 12 | ).toEqual([ 13 | { 14 | functionName: 'e', 15 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 16 | columnNumber: 18, 17 | lineNumber: 25, 18 | _originalColumnNumber: null, 19 | _originalFileName: null, 20 | _originalFunctionName: null, 21 | _originalLineNumber: null, 22 | _originalScriptCode: null, 23 | _scriptCode: null, 24 | }, 25 | { 26 | functionName: 'a', 27 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 28 | columnNumber: 10, 29 | lineNumber: 8, 30 | _originalColumnNumber: null, 31 | _originalFileName: null, 32 | _originalFunctionName: null, 33 | _originalLineNumber: null, 34 | _originalScriptCode: null, 35 | _scriptCode: null, 36 | }, 37 | { 38 | functionName: 'global code', 39 | fileName: 'file:///Users/joe/Documents/Development/OSS/stack-frame/index.html', 40 | columnNumber: 8, 41 | lineNumber: 32, 42 | _originalColumnNumber: null, 43 | _originalFileName: null, 44 | _originalFunctionName: null, 45 | _originalLineNumber: null, 46 | _originalScriptCode: null, 47 | _scriptCode: null, 48 | }, 49 | ]); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/src/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import StackFrame from 'stack-frame'; 3 | 4 | const regexExtractLocation = /\(?(.+?)(?::(\d+))?(?::(\d+))?\)?$/; 5 | 6 | function extractLocation(token: string): [string, number, number] { 7 | return regexExtractLocation.exec(token).slice(1).map(v => { 8 | const p = Number(v); 9 | if (!isNaN(p)) { 10 | return p; 11 | } 12 | return v; 13 | }); 14 | } 15 | 16 | const regexValidFrame_Chrome = /^\s*(at|in)\s.+(:\d+)/; 17 | const regexValidFrame_FireFox = /(^|@)\S+:\d+|.+line\s+\d+\s+>\s+(eval|Function).+/; 18 | 19 | function parseStack(stack: string[]): StackFrame[] { 20 | const frames = stack 21 | .filter( 22 | e => regexValidFrame_Chrome.test(e) || regexValidFrame_FireFox.test(e) 23 | ) 24 | .map(e => { 25 | if (regexValidFrame_FireFox.test(e)) { 26 | // Strip eval, we don't care about it 27 | let isEval = false; 28 | if (/ > (eval|Function)/.test(e)) { 29 | e = e.replace( 30 | / line (\d+)(?: > eval line \d+)* > (eval|Function):\d+:\d+/g, 31 | ':$1' 32 | ); 33 | isEval = true; 34 | } 35 | const data = e.split(/[@]/g); 36 | const last = data.pop(); 37 | return new StackFrame( 38 | data.join('@') || (isEval ? 'eval' : null), 39 | ...extractLocation(last) 40 | ); 41 | } else { 42 | // Strip eval, we don't care about it 43 | if (e.indexOf('(eval ') !== -1) { 44 | e = e.replace(/(\(eval at [^()]*)|(\),.*$)/g, ''); 45 | } 46 | if (e.indexOf('(at ') !== -1) { 47 | e = e.replace(/\(at /, '('); 48 | } 49 | const data = e.trim().split(/\s+/g).slice(1); 50 | const last = data.pop(); 51 | return new StackFrame(data.join(' ') || null, ...extractLocation(last)); 52 | } 53 | }); 54 | return frames; 55 | } 56 | 57 | /** 58 | * Turns an Error, or similar object, into a set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s. 59 | * @alias parse 60 | */ 61 | function parseError(error: Error | string | string[]): StackFrame[] { 62 | if (error == null) { 63 | throw new Error('You cannot pass a null object.'); 64 | } 65 | if (typeof error === 'string') { 66 | return parseStack(error.split('\n')); 67 | } 68 | if (Array.isArray(error)) { 69 | return parseStack(error); 70 | } 71 | if (typeof error.stack === 'string') { 72 | return parseStack(error.stack.split('\n')); 73 | } 74 | throw new Error('The error you provided does not contain a stack trace.'); 75 | } 76 | 77 | export { parseError as parse }; 78 | export default parseError; 79 | -------------------------------------------------------------------------------- /packages/stack-frame-parser/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-regex@^2.0.0: 6 | version "2.1.1" 7 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 8 | 9 | ansi-styles@^2.2.1: 10 | version "2.2.1" 11 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 12 | 13 | chalk@^1.1.3: 14 | version "1.1.3" 15 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 16 | dependencies: 17 | ansi-styles "^2.2.1" 18 | escape-string-regexp "^1.0.2" 19 | has-ansi "^2.0.0" 20 | strip-ansi "^3.0.0" 21 | supports-color "^2.0.0" 22 | 23 | escape-string-regexp@^1.0.2: 24 | version "1.0.5" 25 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 26 | 27 | has-ansi@^2.0.0: 28 | version "2.0.0" 29 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 30 | dependencies: 31 | ansi-regex "^2.0.0" 32 | 33 | stack-frame@0.4.0: 34 | version "0.4.0" 35 | resolved "https://registry.yarnpkg.com/stack-frame/-/stack-frame-0.4.0.tgz#147100cbb78bd69a0a0a318ea2c0af8406ffbb95" 36 | 37 | strip-ansi@^3.0.0: 38 | version "3.0.1" 39 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 40 | dependencies: 41 | ansi-regex "^2.0.0" 42 | 43 | supports-color@^2.0.0: 44 | version "2.0.0" 45 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 46 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/README.md: -------------------------------------------------------------------------------- 1 | # `stack-frame-unmapper` 2 | 3 | Unmaps a stack frame. 4 | 5 | # API 6 | 7 | 8 | 9 | ## unmap 10 | 11 | Turns a set of mapped [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe)s back into their generated code position and enhances them with code. 12 | 13 | **Parameters** 14 | 15 | - `fileUri` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** The URI of the bundle.js file. 16 | - `frames` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<StackFrame>** A set of [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe)s which are already mapped and missing their generated positions. 17 | - `fileContents` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** The number of lines to provide before and after the line specified in the [StackFrame](https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe). (optional, default `3`) 18 | - `contextLines` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** (optional, default `3`) 19 | 20 | Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<StackFrame>>** 21 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/fixtures/bundle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "functionName": "div", 4 | "fileName": "/static/js/bundle.js", 5 | "lineNumber": 41466, 6 | "columnNumber": null, 7 | "_originalFunctionName": "div", 8 | "_originalFileName": "B.js", 9 | "_originalLineNumber": 8, 10 | "_originalColumnNumber": null, 11 | "_scriptCode": [ 12 | { 13 | "lineNumber": 41466, 14 | "content": " 'div',", 15 | "highlight": true 16 | } 17 | ], 18 | "_originalScriptCode": [ 19 | { 20 | "lineNumber": 8, 21 | "content": "
{v}
", 22 | "highlight": true 23 | } 24 | ] 25 | }, 26 | { 27 | "functionName": "B", 28 | "fileName": "/static/js/bundle.js", 29 | "lineNumber": 41395, 30 | "columnNumber": null, 31 | "_originalFunctionName": "B", 32 | "_originalFileName": "A.js", 33 | "_originalLineNumber": 6, 34 | "_originalColumnNumber": null, 35 | "_scriptCode": [ 36 | { 37 | "lineNumber": 41395, 38 | "content": " return _react2.default.createElement(_B2.default, {", 39 | "highlight": true 40 | } 41 | ], 42 | "_originalScriptCode": [ 43 | { 44 | "lineNumber": 6, 45 | "content": " return ()", 46 | "highlight": true 47 | } 48 | ] 49 | }, 50 | { 51 | "functionName": "A", 52 | "fileName": "/static/js/bundle.js", 53 | "lineNumber": 26009, 54 | "columnNumber": null, 55 | "_originalFunctionName": "A", 56 | "_originalFileName": "App.js", 57 | "_originalLineNumber": 8, 58 | "_originalColumnNumber": null, 59 | "_scriptCode": [ 60 | { 61 | "lineNumber": 26009, 62 | "content": " var Ac = _react2.default.createElement(_A2.default, {", 63 | "highlight": true 64 | } 65 | ], 66 | "_originalScriptCode": [ 67 | { 68 | "lineNumber": 8, 69 | "content": " const Ac = ", 70 | "highlight": true 71 | } 72 | ] 73 | }, 74 | { 75 | "functionName": "div", 76 | "fileName": "/static/js/bundle.js", 77 | "lineNumber": 26017, 78 | "columnNumber": null, 79 | "_originalFunctionName": "div", 80 | "_originalFileName": "App.js", 81 | "_originalLineNumber": 10, 82 | "_originalColumnNumber": null, 83 | "_scriptCode": [ 84 | { 85 | "lineNumber": 26017, 86 | "content": " 'div',", 87 | "highlight": true 88 | } 89 | ], 90 | "_originalScriptCode": [ 91 | { 92 | "lineNumber": 10, 93 | "content": "
", 94 | "highlight": true 95 | } 96 | ] 97 | }, 98 | { 99 | "functionName": "App", 100 | "fileName": "/static/js/bundle.js", 101 | "lineNumber": 17412, 102 | "columnNumber": null, 103 | "_originalFunctionName": "App", 104 | "_originalFileName": "index.js", 105 | "_originalLineNumber": 6, 106 | "_originalColumnNumber": null, 107 | "_scriptCode": [ 108 | { 109 | "lineNumber": 17412, 110 | "content": "_reactDom2.default.render(_react2.default.createElement(_App2.default, {", 111 | "highlight": true 112 | } 113 | ], 114 | "_originalScriptCode": [ 115 | { 116 | "lineNumber": 6, 117 | "content": "ReactDOM.render(, document.getElementById('root'));", 118 | "highlight": true 119 | } 120 | ] 121 | } 122 | ] 123 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-frame-unmapper", 3 | "version": "0.4.0", 4 | "description": "Unmaps a stack frame.", 5 | "scripts": { 6 | "prepublishOnly": "npm run build && npm test", 7 | "build": "../../node_modules/.bin/babel src/ -d lib/", 8 | "test": "../../node_modules/.bin/jest" 9 | }, 10 | "main": "lib/index.js", 11 | "repository": "https://github.com/Timer/stack-frame/tree/master/packages/stack-frame-unmapper", 12 | "author": "Joe Haddad ", 13 | "license": "MIT", 14 | "files": [ 15 | "lib/" 16 | ], 17 | "dependencies": { 18 | "babel-runtime": "^6.23.0", 19 | "stack-frame": "0.4.0", 20 | "stack-frame-utils": "0.4.0" 21 | }, 22 | "devDependencies": { 23 | "stack-frame-parser": "0.4.0" 24 | }, 25 | "jest": { 26 | "setupFiles": [ 27 | "./src/__tests__/setupJest.js" 28 | ], 29 | "collectCoverage": true, 30 | "coverageReporters": [ 31 | "json" 32 | ], 33 | "testMatch": [ 34 | "/src/**/__tests__/**/*.js?(x)", 35 | "/src/**/?(*.)(spec|test).js?(x)" 36 | ], 37 | "testPathIgnorePatterns": [ 38 | "/node_modules/", 39 | "/fixtures/", 40 | "setupJest.js" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/src/__tests__/bundle.js: -------------------------------------------------------------------------------- 1 | import { unmap } from '../'; 2 | import { parse } from 'stack-frame-parser'; 3 | import fs from 'fs'; 4 | import { resolve } from 'path'; 5 | 6 | test('basic warning', async () => { 7 | expect.assertions(2); 8 | const error = `Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of \`B\`. See https://fb.me/react-warning-keys for more information. 9 | in div (at B.js:8) 10 | in B (at A.js:6) 11 | in A (at App.js:8) 12 | in div (at App.js:10) 13 | in App (at index.js:6)`; 14 | 15 | fetch.mockResponseOnce( 16 | fs 17 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js')) 18 | .toString('utf8') 19 | ); 20 | fetch.mockResponseOnce( 21 | fs 22 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js.map')) 23 | .toString('utf8') 24 | ); 25 | const frames = await unmap('/static/js/bundle.js', parse(error), 0); 26 | 27 | const expected = JSON.parse( 28 | fs 29 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.json')) 30 | .toString('utf8') 31 | ); 32 | expect(frames).toEqual(expected); 33 | 34 | fetch.mockResponseOnce( 35 | fs 36 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js')) 37 | .toString('utf8') 38 | ); 39 | fetch.mockResponseOnce( 40 | fs 41 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js.map')) 42 | .toString('utf8') 43 | ); 44 | expect(await unmap('/static/js/bundle.js', expected)).toEqual(expected); 45 | }); 46 | 47 | test('default context & unfound source', async () => { 48 | expect.assertions(1); 49 | const error = `Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of \`B\`. See https://fb.me/react-warning-keys for more information. 50 | in div (at B.js:8) 51 | in unknown (at blabla.js:10)`; 52 | 53 | fetch.mockResponseOnce( 54 | fs 55 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js')) 56 | .toString('utf8') 57 | ); 58 | fetch.mockResponseOnce( 59 | fs 60 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js.map')) 61 | .toString('utf8') 62 | ); 63 | const frames = await unmap('/static/js/bundle.js', parse(error)); 64 | expect(frames).toEqual([ 65 | { 66 | functionName: 'div', 67 | fileName: '/static/js/bundle.js', 68 | lineNumber: 41466, 69 | columnNumber: null, 70 | _originalFunctionName: 'div', 71 | _originalFileName: 'B.js', 72 | _originalLineNumber: 8, 73 | _originalColumnNumber: null, 74 | _scriptCode: [ 75 | { 76 | lineNumber: 41463, 77 | content: ' },', 78 | highlight: false, 79 | }, 80 | { 81 | lineNumber: 41464, 82 | content: ' [1, 2].map(function (v) {', 83 | highlight: false, 84 | }, 85 | { 86 | lineNumber: 41465, 87 | content: ' return _react2.default.createElement(', 88 | highlight: false, 89 | }, 90 | { 91 | lineNumber: 41466, 92 | content: " 'div',", 93 | highlight: true, 94 | }, 95 | { 96 | lineNumber: 41467, 97 | content: ' {', 98 | highlight: false, 99 | }, 100 | { 101 | lineNumber: 41468, 102 | content: ' __source: {', 103 | highlight: false, 104 | }, 105 | { 106 | lineNumber: 41469, 107 | content: ' fileName: _jsxFileName,', 108 | highlight: false, 109 | }, 110 | ], 111 | _originalScriptCode: [ 112 | { 113 | lineNumber: 5, 114 | content: ' return (', 115 | highlight: false, 116 | }, 117 | { 118 | lineNumber: 6, 119 | content: '
', 120 | highlight: false, 121 | }, 122 | { 123 | lineNumber: 7, 124 | content: ' {[1, 2].map(v => (', 125 | highlight: false, 126 | }, 127 | { 128 | lineNumber: 8, 129 | content: '
{v}
', 130 | highlight: true, 131 | }, 132 | { 133 | lineNumber: 9, 134 | content: ' ))}', 135 | highlight: false, 136 | }, 137 | { 138 | lineNumber: 10, 139 | content: '
', 140 | highlight: false, 141 | }, 142 | { 143 | lineNumber: 11, 144 | content: ' )', 145 | highlight: false, 146 | }, 147 | ], 148 | }, 149 | { 150 | functionName: null, 151 | fileName: null, 152 | lineNumber: null, 153 | columnNumber: null, 154 | _originalFunctionName: 'unknown', 155 | _originalFileName: 'blabla.js', 156 | _originalLineNumber: 10, 157 | _originalColumnNumber: null, 158 | _scriptCode: null, 159 | _originalScriptCode: null, 160 | }, 161 | ]); 162 | }); 163 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/src/__tests__/setupJest.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | global.fetch = require('jest-fetch-mock'); 3 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/src/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import StackFrame from 'stack-frame'; 3 | import { getSourceMap, getLinesAround } from 'stack-frame-utils'; 4 | import path from 'path'; 5 | 6 | /** 7 | * Turns a set of mapped {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s back into their generated code position and enhances them with code. 8 | * @param {string} fileUri The URI of the bundle.js file. 9 | * @param {StackFrame[]} frames A set of {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}s which are already mapped and missing their generated positions. 10 | * @param {number} [fileContents=3] The number of lines to provide before and after the line specified in the {@link https://github.com/Timer/stack-frame/tree/master/packages/stack-frame#stackframe StackFrame}. 11 | */ 12 | async function unmap( 13 | fileUri: string | { uri: string, contents: string }, 14 | frames: StackFrame[], 15 | contextLines: number = 3 16 | ): Promise { 17 | let fileContents = typeof fileUri === 'object' ? fileUri.contents : null; 18 | fileUri = typeof fileUri === 'object' ? fileUri.uri : fileUri; 19 | if (fileContents == null) { 20 | fileContents = await fetch(fileUri).then(res => res.text()); 21 | } 22 | const map = await getSourceMap(fileUri, fileContents); 23 | return frames.map(frame => { 24 | const { 25 | functionName, 26 | lineNumber, 27 | columnNumber, 28 | _originalLineNumber, 29 | } = frame; 30 | if (_originalLineNumber != null) { 31 | return frame; 32 | } 33 | let { fileName } = frame; 34 | if (fileName) { 35 | fileName = path.normalize(fileName); 36 | } 37 | const splitCache1 = {}, splitCache2 = {}, splitCache3 = {}; 38 | const source = map 39 | .getSources() 40 | .map(s => s.replace(/[\\]+/g, '/')) 41 | .filter(s => { 42 | s = path.normalize(s); 43 | return s.indexOf(fileName) === s.length - fileName.length; 44 | }) 45 | .sort((a, b) => { 46 | a = splitCache1[a] || (splitCache1[a] = a.split(path.sep)); 47 | b = splitCache1[b] || (splitCache1[b] = b.split(path.sep)); 48 | return Math.sign(a.length - b.length); 49 | }) 50 | .sort((a, b) => { 51 | a = splitCache2[a] || (splitCache2[a] = a.split('node_modules')); 52 | b = splitCache2[b] || (splitCache2[b] = b.split('node_modules')); 53 | return Math.sign(a.length - b.length); 54 | }) 55 | .sort((a, b) => { 56 | a = splitCache3[a] || (splitCache3[a] = a.split('~')); 57 | b = splitCache3[b] || (splitCache3[b] = b.split('~')); 58 | return Math.sign(a.length - b.length); 59 | }); 60 | if (source.length < 1) { 61 | return new StackFrame( 62 | null, 63 | null, 64 | null, 65 | null, 66 | null, 67 | functionName, 68 | fileName, 69 | lineNumber, 70 | columnNumber, 71 | null 72 | ); 73 | } 74 | const { line, column } = map.getGeneratedPosition( 75 | source[0], 76 | lineNumber, 77 | columnNumber 78 | ); 79 | const originalSource = map.getSource(source[0]); 80 | return new StackFrame( 81 | functionName, 82 | fileUri, 83 | line, 84 | column || null, 85 | getLinesAround(line, contextLines, fileContents), 86 | functionName, 87 | fileName, 88 | lineNumber, 89 | columnNumber, 90 | getLinesAround(lineNumber, contextLines, originalSource) 91 | ); 92 | }); 93 | } 94 | 95 | export { unmap }; 96 | export default unmap; 97 | -------------------------------------------------------------------------------- /packages/stack-frame-unmapper/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | babel-runtime@^6.23.0: 6 | version "6.23.0" 7 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" 8 | dependencies: 9 | core-js "^2.4.0" 10 | regenerator-runtime "^0.10.0" 11 | 12 | core-js@^2.4.0: 13 | version "2.4.1" 14 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" 15 | 16 | regenerator-runtime@^0.10.0: 17 | version "0.10.3" 18 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e" 19 | 20 | source-map@^0.5.6: 21 | version "0.5.6" 22 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 23 | 24 | stack-frame-parser@0.4.0: 25 | version "0.4.0" 26 | resolved "https://registry.yarnpkg.com/stack-frame-parser/-/stack-frame-parser-0.4.0.tgz#9b7f856fa6a9ef86e5d8cd3b11e33a48f52c1bb6" 27 | dependencies: 28 | stack-frame "0.4.0" 29 | 30 | stack-frame-utils@0.4.0: 31 | version "0.4.0" 32 | resolved "https://registry.yarnpkg.com/stack-frame-utils/-/stack-frame-utils-0.4.0.tgz#3ecd38765690c1e2ee7127057cc11ac3b7038d36" 33 | dependencies: 34 | babel-runtime "^6.23.0" 35 | source-map "^0.5.6" 36 | 37 | stack-frame@0.4.0: 38 | version "0.4.0" 39 | resolved "https://registry.yarnpkg.com/stack-frame/-/stack-frame-0.4.0.tgz#147100cbb78bd69a0a0a318ea2c0af8406ffbb95" 40 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/README.md: -------------------------------------------------------------------------------- 1 | # `stack-frame-utils` 2 | 3 | Utilities for working with stack frames. 4 | 5 | # API 6 | 7 | 8 | 9 | ## getLinesAround 10 | 11 | **Parameters** 12 | 13 | - `line` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** The line number to provide context around. 14 | - `count` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** The number of lines you'd like for context. 15 | - `lines` **([Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)> | [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** The source code. 16 | 17 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<ScriptLine>** 18 | 19 | ## SourceMap 20 | 21 | A wrapped instance of a [SourceMapConsumer](https://github.com/mozilla/source-map). 22 | 23 | This exposes methods which will be indifferent to changes made in [source-map](https://github.com/mozilla/source-map). 24 | 25 | ### getOriginalPosition 26 | 27 | Returns the original code position for a generated code position. 28 | 29 | **Parameters** 30 | 31 | - `line` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** The line of the generated code position. 32 | - `column` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** The column of the generated code position. 33 | 34 | Returns **{source: [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), line: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), column: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)}** 35 | 36 | ### getGeneratedPosition 37 | 38 | Returns the generated code position for an original position. 39 | 40 | **Parameters** 41 | 42 | - `source` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** The source file of the original code position. 43 | - `line` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** The line of the original code position. 44 | - `column` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** The column of the original code position. 45 | 46 | Returns **{line: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number), column: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)}** 47 | 48 | ### getSource 49 | 50 | Returns the code for a given source file name. 51 | 52 | **Parameters** 53 | 54 | - `sourceName` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the source file. 55 | 56 | Returns **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** 57 | 58 | ## getSourceMap 59 | 60 | Returns an instance of [SourceMap](#sourcemap) for a given fileUri and fileContents. 61 | 62 | **Parameters** 63 | 64 | - `fileUri` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** The URI of the source file. 65 | - `fileContents` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** The contents of the source file. 66 | 67 | Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[SourceMap](#sourcemap)>** 68 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/fixtures/inline.es6.js: -------------------------------------------------------------------------------- 1 | function foo() { 2 | console.log('bar') 3 | } 4 | 5 | export { foo } 6 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/fixtures/inline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | function foo() { 7 | console.log('bar'); 8 | } 9 | 10 | exports.foo = foo; 11 | 12 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSxTQUFTLEdBQVQsR0FBZTtBQUNiLFVBQVEsR0FBUixDQUFZLEtBQVo7QUFDRDs7UUFFUSxHLEdBQUEsRyIsImZpbGUiOiJzdGRvdXQiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBmb28oKSB7XG4gIGNvbnNvbGUubG9nKCdiYXInKVxufVxuXG5leHBvcnQgeyBmb28gfVxuIl19 13 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/fixtures/junk-inline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | function foo() { 7 | console.log('bar'); 8 | } 9 | 10 | exports.foo = foo; 11 | 12 | //# sourceMappingURL=data:application/json;charset=utf-8;base64vlq,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSxTQUFTLEdBQVQsR0FBZTtBQUNiLFVBQVEsR0FBUixDQUFZLEtBQVo7QUFDRDs7UUFFUSxHLEdBQUEsRyIsImZpbGUiOiJzdGRvdXQiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBmb28oKSB7XG4gIGNvbnNvbGUubG9nKCdiYXInKVxufVxuXG5leHBvcnQgeyBmb28gfVxuIl19 13 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-frame-utils", 3 | "version": "0.4.0", 4 | "description": "Utilities for working with stack frames.", 5 | "scripts": { 6 | "prepublishOnly": "npm run build && npm test", 7 | "build": "../../node_modules/.bin/babel src/ -d lib/", 8 | "test": "../../node_modules/.bin/jest" 9 | }, 10 | "main": "lib/index.js", 11 | "repository": "https://github.com/Timer/stack-frame/tree/master/packages/stack-frame-utils", 12 | "author": "Joe Haddad ", 13 | "license": "MIT", 14 | "files": [ 15 | "lib/" 16 | ], 17 | "dependencies": { 18 | "babel-runtime": "^6.23.0", 19 | "source-map": "^0.5.6" 20 | }, 21 | "devDependencies": { 22 | "stack-frame": "0.4.0" 23 | }, 24 | "peerDependencies": { 25 | "stack-frame": "0.4.0" 26 | }, 27 | "jest": { 28 | "setupFiles": [ 29 | "./src/__tests__/setupJest.js" 30 | ], 31 | "collectCoverage": true, 32 | "coverageReporters": [ 33 | "json" 34 | ], 35 | "testMatch": [ 36 | "/src/**/__tests__/**/*.js?(x)", 37 | "/src/**/?(*.)(spec|test).js?(x)" 38 | ], 39 | "testPathIgnorePatterns": [ 40 | "/node_modules/", 41 | "/fixtures/", 42 | "setupJest.js" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/__tests__/__snapshots__/lines-around.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should return lines around from a string 1`] = ` 4 | Array [ 5 | ScriptLine { 6 | "content": "two", 7 | "highlight": false, 8 | "lineNumber": 2, 9 | }, 10 | ScriptLine { 11 | "content": "three", 12 | "highlight": false, 13 | "lineNumber": 3, 14 | }, 15 | ScriptLine { 16 | "content": "four", 17 | "highlight": true, 18 | "lineNumber": 4, 19 | }, 20 | ScriptLine { 21 | "content": "five", 22 | "highlight": false, 23 | "lineNumber": 5, 24 | }, 25 | ScriptLine { 26 | "content": "six", 27 | "highlight": false, 28 | "lineNumber": 6, 29 | }, 30 | ] 31 | `; 32 | 33 | exports[`should return lines around from an array 1`] = ` 34 | Array [ 35 | ScriptLine { 36 | "content": "two", 37 | "highlight": false, 38 | "lineNumber": 2, 39 | }, 40 | ScriptLine { 41 | "content": "three", 42 | "highlight": false, 43 | "lineNumber": 3, 44 | }, 45 | ScriptLine { 46 | "content": "four", 47 | "highlight": true, 48 | "lineNumber": 4, 49 | }, 50 | ScriptLine { 51 | "content": "five", 52 | "highlight": false, 53 | "lineNumber": 5, 54 | }, 55 | ScriptLine { 56 | "content": "six", 57 | "highlight": false, 58 | "lineNumber": 6, 59 | }, 60 | ] 61 | `; 62 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/__tests__/extract-source-map.js: -------------------------------------------------------------------------------- 1 | import { extractSourceMapUrl } from '../getSourceMap'; 2 | 3 | test('extracts last source map directive', async () => { 4 | const res = await extractSourceMapUrl( 5 | `test.js`, 6 | `//# sourceMappingURL=test.js.map\nconsole.log('a')\n//# sourceMappingURL=bundle.js.map` 7 | ); 8 | expect(res).toBe('bundle.js.map'); 9 | }); 10 | 11 | test('errors when no source map', async () => { 12 | expect.assertions(1); 13 | 14 | const testFileName = 'test.js'; 15 | try { 16 | await extractSourceMapUrl( 17 | testFileName, 18 | `console.log('hi')\n\nconsole.log('bye')` 19 | ); 20 | } catch (e) { 21 | expect(e).toBe(`Cannot find a source map directive for ${testFileName}.`); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/__tests__/get-source-map.js: -------------------------------------------------------------------------------- 1 | import { getSourceMap } from '../'; 2 | import fs from 'fs'; 3 | import { resolve } from 'path'; 4 | 5 | test('finds an external source map', async () => { 6 | const file = fs 7 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js')) 8 | .toString('utf8'); 9 | fetch.mockResponseOnce( 10 | fs 11 | .readFileSync(resolve(__dirname, '../../fixtures/bundle.js.map')) 12 | .toString('utf8') 13 | ); 14 | 15 | const sm = await getSourceMap('/', file); 16 | expect(sm.getOriginalPosition(26122, 21)).toEqual({ 17 | line: 7, 18 | column: 0, 19 | source: 'webpack:///packages/react-scripts/template/src/App.js', 20 | }); 21 | }); 22 | 23 | test('find an inline source map', async () => { 24 | const sourceName = 'test.js'; 25 | 26 | const file = fs 27 | .readFileSync(resolve(__dirname, '../../fixtures/inline.js')) 28 | .toString('utf8'); 29 | const fileO = fs 30 | .readFileSync(resolve(__dirname, '../../fixtures/inline.es6.js')) 31 | .toString('utf8'); 32 | 33 | const sm = await getSourceMap('/', file); 34 | expect(sm.getSources()).toEqual([sourceName]); 35 | expect(sm.getSource(sourceName)).toBe(fileO); 36 | expect(sm.getGeneratedPosition(sourceName, 5, 10)).toEqual({ 37 | line: 10, 38 | column: 8, 39 | }); 40 | }); 41 | 42 | test('error on a source map with unsupported encoding', async () => { 43 | expect.assertions(2); 44 | 45 | const file = fs 46 | .readFileSync(resolve(__dirname, '../../fixtures/junk-inline.js')) 47 | .toString('utf8'); 48 | try { 49 | await getSourceMap('/', file); 50 | } catch (e) { 51 | expect(e instanceof Error).toBe(true); 52 | expect(e.message).toBe( 53 | 'Sorry, non-base64 inline source-map encoding is not supported.' 54 | ); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/__tests__/lines-around.js: -------------------------------------------------------------------------------- 1 | import { getLinesAround } from '../'; 2 | 3 | const arr = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight']; 4 | 5 | test('should return lines around from a string', () => { 6 | expect(getLinesAround(4, 2, arr)).toMatchSnapshot(); 7 | }); 8 | 9 | test('should return lines around from an array', () => { 10 | expect(getLinesAround(4, 2, arr.join('\n'))).toMatchSnapshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/__tests__/setupJest.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | global.fetch = require('jest-fetch-mock'); 3 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/getLinesAround.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import { ScriptLine } from 'stack-frame'; 3 | 4 | /** 5 | * 6 | * @param {number} line The line number to provide context around. 7 | * @param {number} count The number of lines you'd like for context. 8 | * @param {string[] | string} lines The source code. 9 | */ 10 | function getLinesAround( 11 | line: number, 12 | count: number, 13 | lines: string[] | string 14 | ): ScriptLine[] { 15 | if (typeof lines === 'string') { 16 | lines = lines.split('\n'); 17 | } 18 | const result = []; 19 | for ( 20 | let index = Math.max(0, line - 1 - count); 21 | index <= Math.min(lines.length - 1, line - 1 + count); 22 | ++index 23 | ) { 24 | result.push(new ScriptLine(index + 1, lines[index], index === line - 1)); 25 | } 26 | return result; 27 | } 28 | 29 | export { getLinesAround }; 30 | export default getLinesAround; 31 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/getSourceMap.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import { SourceMapConsumer } from 'source-map'; 3 | 4 | /** 5 | * A wrapped instance of a {@link https://github.com/mozilla/source-map SourceMapConsumer}. 6 | * 7 | * This exposes methods which will be indifferent to changes made in {@link https://github.com/mozilla/source-map source-map}. 8 | */ 9 | class SourceMap { 10 | __source_map: SourceMapConsumer; 11 | 12 | constructor(sourceMap) { 13 | this.__source_map = sourceMap; 14 | } 15 | 16 | /** 17 | * Returns the original code position for a generated code position. 18 | * @param {number} line The line of the generated code position. 19 | * @param {number} column The column of the generated code position. 20 | */ 21 | getOriginalPosition( 22 | line: number, 23 | column: number 24 | ): { source: string, line: number, column: number } { 25 | const { 26 | line: l, 27 | column: c, 28 | source: s, 29 | } = this.__source_map.originalPositionFor({ 30 | line, 31 | column, 32 | }); 33 | return { line: l, column: c, source: s }; 34 | } 35 | 36 | /** 37 | * Returns the generated code position for an original position. 38 | * @param {string} source The source file of the original code position. 39 | * @param {number} line The line of the original code position. 40 | * @param {number} column The column of the original code position. 41 | */ 42 | getGeneratedPosition( 43 | source: string, 44 | line: number, 45 | column: number 46 | ): { line: number, column: number } { 47 | const { line: l, column: c } = this.__source_map.generatedPositionFor({ 48 | source, 49 | line, 50 | column, 51 | }); 52 | return { 53 | line: l, 54 | column: c, 55 | }; 56 | } 57 | 58 | /** 59 | * Returns the code for a given source file name. 60 | * @param {string} sourceName The name of the source file. 61 | */ 62 | getSource(sourceName: string): string { 63 | return this.__source_map.sourceContentFor(sourceName); 64 | } 65 | 66 | getSources(): string[] { 67 | return this.__source_map.sources; 68 | } 69 | } 70 | 71 | function extractSourceMapUrl(fileUri: string, fileContents: string) { 72 | const regex = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/gm; 73 | let match = null; 74 | for (;;) { 75 | let next = regex.exec(fileContents); 76 | if (next == null) { 77 | break; 78 | } 79 | match = next; 80 | } 81 | if (!(match && match[1])) { 82 | return Promise.reject(`Cannot find a source map directive for ${fileUri}.`); 83 | } 84 | return Promise.resolve(match[1].toString()); 85 | } 86 | 87 | /** 88 | * Returns an instance of {@link SourceMap} for a given fileUri and fileContents. 89 | * @param {string} fileUri The URI of the source file. 90 | * @param {string} fileContents The contents of the source file. 91 | */ 92 | async function getSourceMap( 93 | fileUri: string, 94 | fileContents: string 95 | ): Promise { 96 | let sm = await extractSourceMapUrl(fileUri, fileContents); 97 | if (sm.indexOf('data:') === 0) { 98 | const base64 = /^data:application\/json;([\w=:"-]+;)*base64,/; 99 | const match2 = sm.match(base64); 100 | if (!match2) { 101 | throw new Error( 102 | 'Sorry, non-base64 inline source-map encoding is not supported.' 103 | ); 104 | } 105 | sm = sm.substring(match2[0].length); 106 | sm = window.atob(sm); 107 | sm = JSON.parse(sm); 108 | return new SourceMap(new SourceMapConsumer(sm)); 109 | } else { 110 | const index = fileUri.lastIndexOf('/'); 111 | const url = fileUri.substring(0, index + 1) + sm; 112 | const obj = await fetch(url).then(res => res.json()); 113 | return new SourceMap(new SourceMapConsumer(obj)); 114 | } 115 | } 116 | 117 | export { extractSourceMapUrl, getSourceMap }; 118 | export default getSourceMap; 119 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/src/index.js: -------------------------------------------------------------------------------- 1 | import { getSourceMap } from './getSourceMap'; 2 | import { getLinesAround } from './getLinesAround'; 3 | export { getSourceMap, getLinesAround }; 4 | -------------------------------------------------------------------------------- /packages/stack-frame-utils/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | babel-runtime@^6.23.0: 6 | version "6.23.0" 7 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" 8 | dependencies: 9 | core-js "^2.4.0" 10 | regenerator-runtime "^0.10.0" 11 | 12 | core-js@^2.4.0: 13 | version "2.4.1" 14 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" 15 | 16 | regenerator-runtime@^0.10.0: 17 | version "0.10.3" 18 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e" 19 | 20 | source-map@^0.5.6: 21 | version "0.5.6" 22 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 23 | 24 | stack-frame@0.4.0: 25 | version "0.4.0" 26 | resolved "https://registry.yarnpkg.com/stack-frame/-/stack-frame-0.4.0.tgz#147100cbb78bd69a0a0a318ea2c0af8406ffbb95" 27 | -------------------------------------------------------------------------------- /packages/stack-frame/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /packages/stack-frame/.npmignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | -------------------------------------------------------------------------------- /packages/stack-frame/README.md: -------------------------------------------------------------------------------- 1 | # `stack-frame` 2 | 3 | A stack frame. 4 | 5 | # API 6 | 7 | 8 | 9 | ## ScriptLine 10 | 11 | A container holding a script line. 12 | 13 | ### lineNumber 14 | 15 | The line number of this line of source. 16 | 17 | Type: [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) 18 | 19 | ### content 20 | 21 | The content (or value) of this line of source. 22 | 23 | Type: [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) 24 | 25 | ### highlight 26 | 27 | Whether or not this line should be highlighted. Particularly useful for error reporting with context. 28 | 29 | Type: [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) 30 | 31 | ## StackFrame 32 | 33 | A representation of a stack frame. 34 | 35 | ### getFunctionName 36 | 37 | Returns the name of this function. 38 | 39 | Returns **([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) | null)** 40 | 41 | ### getSource 42 | 43 | Returns the source of the frame. 44 | This contains the file name, line number, and column number when available. 45 | 46 | Returns **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** 47 | 48 | ### toString 49 | 50 | Returns a pretty version of this stack frame. 51 | 52 | Returns **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** 53 | -------------------------------------------------------------------------------- /packages/stack-frame/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-frame", 3 | "version": "0.4.0", 4 | "description": "A stack frame.", 5 | "scripts": { 6 | "prepublishOnly": "npm run build && npm test", 7 | "build": "../../node_modules/.bin/babel src/ -d lib/", 8 | "test": "../../node_modules/.bin/jest" 9 | }, 10 | "main": "lib/index.js", 11 | "repository": "https://github.com/Timer/stack-frame/tree/master/packages/stack-frame", 12 | "author": "Joe Haddad ", 13 | "license": "MIT", 14 | "files": [ 15 | "lib/" 16 | ], 17 | "jest": { 18 | "collectCoverage": true, 19 | "coverageThreshold": { 20 | "global": { 21 | "statements": 100 22 | } 23 | }, 24 | "coverageReporters": [ 25 | "json" 26 | ], 27 | "testMatch": [ 28 | "/src/**/__tests__/**/*.js?(x)", 29 | "/src/**/?(*.)(spec|test).js?(x)" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stack-frame/src/__tests__/__snapshots__/script-line.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`script line shape 1`] = ` 4 | ScriptLine { 5 | "content": "foobar", 6 | "highlight": true, 7 | "lineNumber": 5, 8 | } 9 | `; 10 | 11 | exports[`script line to provide default highlight 1`] = ` 12 | ScriptLine { 13 | "content": "foobar", 14 | "highlight": false, 15 | "lineNumber": 5, 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /packages/stack-frame/src/__tests__/__snapshots__/stack-frame.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`proper empty shape 1`] = ` 4 | StackFrame { 5 | "_originalColumnNumber": null, 6 | "_originalFileName": null, 7 | "_originalFunctionName": null, 8 | "_originalLineNumber": null, 9 | "_originalScriptCode": null, 10 | "_scriptCode": null, 11 | "columnNumber": null, 12 | "fileName": null, 13 | "functionName": null, 14 | "lineNumber": null, 15 | } 16 | `; 17 | 18 | exports[`proper full shape 1`] = ` 19 | StackFrame { 20 | "_originalColumnNumber": 13, 21 | "_originalFileName": "test.js", 22 | "_originalFunctionName": "apple", 23 | "_originalLineNumber": 37, 24 | "_originalScriptCode": null, 25 | "_scriptCode": null, 26 | "columnNumber": 37, 27 | "fileName": "b.js", 28 | "functionName": "a", 29 | "lineNumber": 13, 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /packages/stack-frame/src/__tests__/script-line.js: -------------------------------------------------------------------------------- 1 | import { ScriptLine } from '../'; 2 | 3 | test('script line shape', () => { 4 | expect(new ScriptLine(5, 'foobar', true)).toMatchSnapshot(); 5 | }); 6 | 7 | test('script line to provide default highlight', () => { 8 | expect(new ScriptLine(5, 'foobar')).toMatchSnapshot(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/stack-frame/src/__tests__/stack-frame.js: -------------------------------------------------------------------------------- 1 | import { StackFrame } from '../'; 2 | 3 | test('proper empty shape', () => { 4 | const empty = new StackFrame(); 5 | expect(empty).toMatchSnapshot(); 6 | 7 | expect(empty.getFunctionName()).toBe(null); 8 | expect(empty.getSource()).toBe(''); 9 | expect(empty.toString()).toBe(''); 10 | }); 11 | 12 | test('proper full shape', () => { 13 | const empty = new StackFrame( 14 | 'a', 15 | 'b.js', 16 | 13, 17 | 37, 18 | undefined, 19 | 'apple', 20 | 'test.js', 21 | 37, 22 | 13 23 | ); 24 | expect(empty).toMatchSnapshot(); 25 | 26 | expect(empty.getFunctionName()).toBe('a'); 27 | expect(empty.getSource()).toBe('b.js:13:37'); 28 | expect(empty.toString()).toBe('a (b.js:13:37)'); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/stack-frame/src/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | /** A container holding a script line. */ 4 | class ScriptLine { 5 | /** The line number of this line of source. */ 6 | lineNumber: number; 7 | /** The content (or value) of this line of source. */ 8 | content: string; 9 | /** Whether or not this line should be highlighted. Particularly useful for error reporting with context. */ 10 | highlight: boolean; 11 | 12 | constructor(lineNumber: number, content: string, highlight: boolean = false) { 13 | this.lineNumber = lineNumber; 14 | this.content = content; 15 | this.highlight = highlight; 16 | } 17 | } 18 | 19 | /** 20 | * A representation of a stack frame. 21 | */ 22 | class StackFrame { 23 | functionName: string | null; 24 | fileName: string | null; 25 | lineNumber: number | null; 26 | columnNumber: number | null; 27 | 28 | _originalFunctionName: string | null; 29 | _originalFileName: string | null; 30 | _originalLineNumber: number | null; 31 | _originalColumnNumber: number | null; 32 | 33 | _scriptCode: ScriptLine[] | null; 34 | _originalScriptCode: ScriptLine[] | null; 35 | 36 | constructor( 37 | functionName: string | null = null, 38 | fileName: string | null = null, 39 | lineNumber: number | null = null, 40 | columnNumber: number | null = null, 41 | scriptCode: ScriptLine[] | null = null, 42 | sourceFunctionName: string | null = null, 43 | sourceFileName: string | null = null, 44 | sourceLineNumber: number | null = null, 45 | sourceColumnNumber: number | null = null, 46 | sourceScriptCode: ScriptLine[] | null = null 47 | ) { 48 | this.functionName = functionName; 49 | 50 | this.fileName = fileName; 51 | this.lineNumber = lineNumber; 52 | this.columnNumber = columnNumber; 53 | 54 | this._originalFunctionName = sourceFunctionName; 55 | this._originalFileName = sourceFileName; 56 | this._originalLineNumber = sourceLineNumber; 57 | this._originalColumnNumber = sourceColumnNumber; 58 | 59 | this._scriptCode = scriptCode; 60 | this._originalScriptCode = sourceScriptCode; 61 | } 62 | 63 | /** 64 | * Returns the name of this function. 65 | */ 66 | getFunctionName(): string | null { 67 | return this.functionName; 68 | } 69 | 70 | /** 71 | * Returns the source of the frame. 72 | * This contains the file name, line number, and column number when available. 73 | */ 74 | getSource(): string { 75 | let str = ''; 76 | if (this.fileName != null) { 77 | str += this.fileName + ':'; 78 | } 79 | if (this.lineNumber != null) { 80 | str += this.lineNumber + ':'; 81 | } 82 | if (this.columnNumber != null) { 83 | str += this.columnNumber + ':'; 84 | } 85 | return str.slice(0, -1); 86 | } 87 | 88 | /** 89 | * Returns a pretty version of this stack frame. 90 | */ 91 | toString(): string { 92 | const f = this.getFunctionName(); 93 | if (f == null) { 94 | return this.getSource(); 95 | } 96 | return `${f} (${this.getSource()})`; 97 | } 98 | } 99 | 100 | export { StackFrame, ScriptLine }; 101 | export default StackFrame; 102 | -------------------------------------------------------------------------------- /packages/stack-frame/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /tasks/test.sh: -------------------------------------------------------------------------------- 1 | cd "$(dirname "$0")" 2 | cd .. 3 | 4 | set -x 5 | 6 | lerna run build 7 | lerna run test 8 | --------------------------------------------------------------------------------