diff --git a/client/modules/user-menu/index.js b/client/modules/user-menu/index.js index 58f3ff1c..27e48baa 100644 --- a/client/modules/user-menu/index.js +++ b/client/modules/user-menu/index.js @@ -7,10 +7,13 @@ require('../../../css/user-menu.css'); const currify = require('currify/legacy'); const wraptile = require('wraptile/legacy'); const {promisify} = require('es6-promisify'); +const fullstore = require('fullstore/legacy'); const load = require('load.js'); const createElement = require('@cloudcmd/create-element'); const tryCatch = require('try-catch'); const tryToCatch = require('try-to-catch/legacy'); +const parse = require('./parse-error'); +const {codeFrameColumns} = require('@babel/code-frame'); const Images = require('../../dom/images'); const Dialog = require('../../dom/dialog'); @@ -18,6 +21,7 @@ const getUserMenu = require('./get-user-menu'); const navigate = require('./navigate'); const loadCSS = promisify(load.css); +const sourceStore = fullstore(); const Name = 'UserMenu'; CloudCmd[Name] = module.exports; @@ -45,12 +49,15 @@ async function show() { const {dirPath} = CurrentInfo; const res = await fetch(`${CloudCmd.prefix}/api/v1/user-menu?dir=${dirPath}`); - const [error, userMenu] = tryCatch(getUserMenu, await res.text()); + const source = await res.text(); + const [error, userMenu] = tryCatch(getUserMenu, source); Images.hide(); if (error) - return Dialog.alert(`User menu error: ${error.message}`); + return Dialog.alert(getCodeFrame({error, source})); + + sourceStore(source); const options = Object .keys(userMenu) @@ -132,18 +139,41 @@ const onKeyDown = currify(async (keys, options, userMenu, e) => { const runUserMenu = async (value, options, userMenu) => { hide(); - const [e] = await tryToCatch(userMenu[value], { + const [error] = await tryToCatch(userMenu[value], { DOM, CloudCmd, tryToCatch, }); - if (!e) + if (!error) return; - if (e.name === 'Error') - return Dialog.alert(e.message); - - return Dialog.alert(e.stack); + const source = sourceStore(); + return Dialog.alert(getCodeFrame({ + error, + source, + })); }; +function getCodeFrame({error, source}) { + if (error.code === 'frame') + return error.message; + + const [line, column] = parse(error); + const start = { + line, + column, + }; + + const location = { + start, + }; + + const frame = codeFrameColumns(source, location, { + message: error.message, + highlightCode: false, + }); + + return `
${frame}
`; +} + diff --git a/client/modules/user-menu/parse-error.js b/client/modules/user-menu/parse-error.js new file mode 100644 index 00000000..46ca1020 --- /dev/null +++ b/client/modules/user-menu/parse-error.js @@ -0,0 +1,29 @@ +'use strict'; + +const isNumber = (a) => typeof a === 'number'; + +module.exports = (error) => { + const { + lineNumber, + columnNumber, + } = error; + + if (isNumber(lineNumber) && isNumber(columnNumber)) + return [ + lineNumber, + columnNumber, + ]; + + const before = error.stack.indexOf('>'); + const str = error.stack.slice(before + 1); + const after = str.indexOf(')'); + const newStr = str.slice(1, after); + + const [line, column] = newStr.split(':'); + + return [ + Number(line), + Number(column), + ]; +}; + diff --git a/client/modules/user-menu/parse-error.spec.js b/client/modules/user-menu/parse-error.spec.js new file mode 100644 index 00000000..ebd21553 --- /dev/null +++ b/client/modules/user-menu/parse-error.spec.js @@ -0,0 +1,32 @@ +'use strict'; + +const test = require('supertape'); +const parseError = require('./parse-error'); + +test('user-menu: parse-error', (t) => { + const result = parseError({ + lineNumber: 1, + columnNumber: 2, + }); + + const expected = [1, 2]; + + t.deepEqual(result, expected); + t.end(); +}); + +test('user-menu: parse-error', (t) => { + const stack = ` + ReferenceError: s is not defined + at eval (eval at module.exports (get-user-menu.js:NaN), :1:2) + at module.exports (get-user-menu.js:6) + at tryCatch (VM12611 try-catch.js:7) + at AsyncFunction.show (index.js:67) + `; + + const result = parseError({stack}); + const expected = [1, 2]; + + t.deepEqual(result, expected); + t.end(); +}); diff --git a/package.json b/package.json index c9231ab1..05c3b351 100644 --- a/package.json +++ b/package.json @@ -162,6 +162,7 @@ "writejson": "^2.0.0" }, "devDependencies": { + "@babel/code-frame": "^7.5.5", "@babel/core": "^7.0.0", "@babel/preset-env": "^7.0.0", "@cloudcmd/clipboard": "^1.0.2", diff --git a/server/user-menu.js b/server/user-menu.js index 318d9158..b297648c 100644 --- a/server/user-menu.js +++ b/server/user-menu.js @@ -72,13 +72,16 @@ async function onGET({req, res, menuName}) { if (parseError) return res .type('js') - .send(` - throw Error(\`
path: ${menuPath}\n\n${codeframe({
-    error: parseError,
-    source,
-    highlightCode: false,
-})}
+            .send(`const e = Error(\`
path: ${menuPath}\n\n${codeframe({
+                error: parseError,
+                source,
+                highlightCode: false,
+            })}
                 
\`); + + e.code = 'frame'; + + throw e; `); res