diff --git a/package.json b/package.json index a1df7345..db9da8bb 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^5.11.6", + "@types/babel__code-frame": "^7.0.2", "jest-in-case": "^1.0.2", "jest-serializer-ansi": "^1.0.3", "jest-watch-select-projects": "^2.0.0", diff --git a/src/get-user-code-frame.js b/src/get-user-code-frame.js deleted file mode 100644 index 7cdb90b6..00000000 --- a/src/get-user-code-frame.js +++ /dev/null @@ -1,65 +0,0 @@ -// We try to load node dependencies -let chalk = null -let readFileSync = null -let codeFrameColumns = null - -try { - const nodeRequire = module && module.require - - readFileSync = nodeRequire.call(module, 'fs').readFileSync - codeFrameColumns = nodeRequire.call(module, '@babel/code-frame') - .codeFrameColumns - chalk = nodeRequire.call(module, 'chalk') -} catch { - // We're in a browser environment -} - -// frame has the form "at myMethod (location/to/my/file.js:10:2)" -function getCodeFrame(frame) { - const locationStart = frame.indexOf('(') + 1 - const locationEnd = frame.indexOf(')') - const frameLocation = frame.slice(locationStart, locationEnd) - - const frameLocationElements = frameLocation.split(':') - const [filename, line, column] = [ - frameLocationElements[0], - parseInt(frameLocationElements[1], 10), - parseInt(frameLocationElements[2], 10), - ] - - let rawFileContents = '' - try { - rawFileContents = readFileSync(filename, 'utf-8') - } catch { - return '' - } - - const codeFrame = codeFrameColumns( - rawFileContents, - { - start: {line, column}, - }, - { - highlightCode: true, - linesBelow: 0, - }, - ) - return `${chalk.dim(frameLocation)}\n${codeFrame}\n` -} - -function getUserCodeFrame() { - // If we couldn't load dependencies, we can't generate the user trace - /* istanbul ignore next */ - if (!readFileSync || !codeFrameColumns) { - return '' - } - const err = new Error() - const firstClientCodeFrame = err.stack - .split('\n') - .slice(1) // Remove first line which has the form "Error: TypeError" - .find(frame => !frame.includes('node_modules/')) // Ignore frames from 3rd party libraries - - return getCodeFrame(firstClientCodeFrame) -} - -export {getUserCodeFrame} diff --git a/src/get-user-code-frame.ts b/src/get-user-code-frame.ts new file mode 100644 index 00000000..1476a301 --- /dev/null +++ b/src/get-user-code-frame.ts @@ -0,0 +1,63 @@ +// type-only imports get erased at runtime +import type FS from 'fs' +import type {Chalk} from 'chalk' +import type {codeFrameColumns as CodeFrameColumnsFn} from '@babel/code-frame' + +// frame has the form "at myMethod (location/to/my/file.js:10:2)" +function getCodeFrame(frame: string) { + const locationStart = frame.indexOf('(') + 1 + const locationEnd = frame.indexOf(')') + const frameLocation = frame.slice(locationStart, locationEnd) + + const frameLocationElements = frameLocation.split(':') + const [filename, line, column] = [ + frameLocationElements[0], + parseInt(frameLocationElements[1], 10), + parseInt(frameLocationElements[2], 10), + ] + + // use require() instead of a top-level import for Node dependencies + // so that errors thrown when called in a browser environment can be caught and handled. + // expect to throw errors here and catch them in `getUserCodeFrame`. + + const {readFileSync} = module.require('fs') as typeof FS + const rawFileContents = readFileSync(filename, 'utf-8') + + const {codeFrameColumns} = module.require('@babel/code-frame') as { + codeFrameColumns: typeof CodeFrameColumnsFn + } + const codeFrame = codeFrameColumns( + rawFileContents, + { + start: {line, column}, + }, + { + highlightCode: true, + linesBelow: 0, + }, + ) + + const chalk = module.require('chalk') as Chalk + + return `${chalk.dim(frameLocation)}\n${codeFrame}\n` +} + +function getUserCodeFrame(): string { + try { + const err = new Error() + const firstClientCodeFrame = err.stack + ?.split('\n') + .slice(1) // Remove first line which has the form "Error: TypeError" + .find(frame => !frame.includes('node_modules/')) // Ignore frames from 3rd party libraries + + /* istanbul ignore next */ + if (!firstClientCodeFrame) return '' + return getCodeFrame(firstClientCodeFrame) + } catch { + // Expect to throw and catch errors when in the browser + // If we couldn't load dependencies, we can't generate the user trace + return '' + } +} + +export {getUserCodeFrame}