Improves error reporting in code editor
This commit is contained in:
parent
d1a3860bb1
commit
35b0ba27e6
7 changed files with 169 additions and 82 deletions
|
@ -1,20 +1,20 @@
|
||||||
import Editor, { Monaco } from '@monaco-editor/react'
|
import Editor, { Monaco } from '@monaco-editor/react'
|
||||||
import useTheme from 'hooks/useTheme'
|
import useTheme from 'hooks/useTheme'
|
||||||
import prettier from 'prettier/standalone'
|
|
||||||
import parserTypeScript from 'prettier/parser-typescript'
|
|
||||||
// import codeAsString from './code-as-string'
|
|
||||||
import typesImport from './types-import'
|
import typesImport from './types-import'
|
||||||
import React, { useCallback, useEffect, useRef } from 'react'
|
import React, { useCallback, useEffect, useRef } from 'react'
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
||||||
|
import { getFormattedCode } from 'utils/code'
|
||||||
|
|
||||||
export type IMonaco = typeof monaco
|
export type IMonaco = typeof monaco
|
||||||
|
|
||||||
export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
|
export type IMonacoEditor = monaco.editor.IStandaloneCodeEditor
|
||||||
|
|
||||||
|
const modifierKeys = ['Escape', 'Meta', 'Control', 'Shift', 'Option', 'Alt']
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string
|
value: string
|
||||||
error: { line: number }
|
error: { line: number; column: number }
|
||||||
fontSize: number
|
fontSize: number
|
||||||
monacoRef?: React.MutableRefObject<IMonaco>
|
monacoRef?: React.MutableRefObject<IMonaco>
|
||||||
editorRef?: React.MutableRefObject<IMonacoEditor>
|
editorRef?: React.MutableRefObject<IMonacoEditor>
|
||||||
|
@ -92,13 +92,7 @@ export default function CodeEditor({
|
||||||
monaco.languages.registerDocumentFormattingEditProvider('typescript', {
|
monaco.languages.registerDocumentFormattingEditProvider('typescript', {
|
||||||
async provideDocumentFormattingEdits(model) {
|
async provideDocumentFormattingEdits(model) {
|
||||||
try {
|
try {
|
||||||
const text = prettier.format(model.getValue(), {
|
const text = getFormattedCode(model.getValue())
|
||||||
parser: 'typescript',
|
|
||||||
plugins: [parserTypeScript],
|
|
||||||
singleQuote: true,
|
|
||||||
trailingComma: 'es5',
|
|
||||||
semi: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -126,6 +120,8 @@ export default function CodeEditor({
|
||||||
|
|
||||||
editor.updateOptions({
|
editor.updateOptions({
|
||||||
fontSize,
|
fontSize,
|
||||||
|
fontFamily: "'Recursive', monospace",
|
||||||
|
fontWeight: '420',
|
||||||
wordBasedSuggestions: false,
|
wordBasedSuggestions: false,
|
||||||
minimap: { enabled: false },
|
minimap: { enabled: false },
|
||||||
lightbulb: {
|
lightbulb: {
|
||||||
|
@ -141,8 +137,8 @@ export default function CodeEditor({
|
||||||
|
|
||||||
const handleKeydown = useCallback(
|
const handleKeydown = useCallback(
|
||||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
onKey && onKey()
|
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
!modifierKeys.includes(e.key) && onKey?.()
|
||||||
const metaKey = navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey
|
const metaKey = navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey
|
||||||
if (e.key === 's' && metaKey) {
|
if (e.key === 's' && metaKey) {
|
||||||
const editor = rEditor.current
|
const editor = rEditor.current
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
import styled from 'styles'
|
import styled from 'styles'
|
||||||
import { useStateDesigner } from '@state-designer/react'
|
import { useStateDesigner } from '@state-designer/react'
|
||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useCallback, useEffect, useRef } from 'react'
|
||||||
import state, { useSelector } from 'state'
|
import state, { useSelector } from 'state'
|
||||||
import { CodeFile } from 'types'
|
import { CodeError, CodeFile, CodeResult } from 'types'
|
||||||
import CodeDocs from './code-docs'
|
import CodeDocs from './code-docs'
|
||||||
import { generateFromCode } from 'state/code/generate'
|
import { generateFromCode } from 'state/code/generate'
|
||||||
import * as Panel from '../panel'
|
import * as Panel from '../panel'
|
||||||
|
@ -19,16 +19,9 @@ import {
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
const CodeEditor = dynamic(() => import('./code-editor'))
|
const CodeEditor = dynamic(() => import('./code-editor'))
|
||||||
|
|
||||||
const getErrorLineAndColumn = (e: any) => {
|
const increaseCodeSize = () => state.send('INCREASED_CODE_FONT_SIZE')
|
||||||
if ('line' in e) {
|
const decreaseCodeSize = () => state.send('DECREASED_CODE_FONT_SIZE')
|
||||||
return { line: Number(e.line), column: e.column }
|
const toggleCodePanel = () => state.send('TOGGLED_CODE_PANEL_OPEN')
|
||||||
}
|
|
||||||
|
|
||||||
const result = e.stack.match(/:([0-9]+):([0-9]+)/)
|
|
||||||
if (result) {
|
|
||||||
return { line: Number(result[1]) - 1, column: result[2] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CodePanel(): JSX.Element {
|
export default function CodePanel(): JSX.Element {
|
||||||
const rContainer = useRef<HTMLDivElement>(null)
|
const rContainer = useRef<HTMLDivElement>(null)
|
||||||
|
@ -43,7 +36,7 @@ export default function CodePanel(): JSX.Element {
|
||||||
const local = useStateDesigner({
|
const local = useStateDesigner({
|
||||||
data: {
|
data: {
|
||||||
code: file.code,
|
code: file.code,
|
||||||
error: null as { message: string; line: number; column: number } | null,
|
error: null as CodeError | null,
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
MOUNTED: 'setCode',
|
MOUNTED: 'setCode',
|
||||||
|
@ -53,13 +46,23 @@ export default function CodePanel(): JSX.Element {
|
||||||
states: {
|
states: {
|
||||||
editingCode: {
|
editingCode: {
|
||||||
on: {
|
on: {
|
||||||
RAN_CODE: ['saveCode', 'runCode'],
|
RAN_CODE: { do: 'saveCode', to: 'evaluatingCode' },
|
||||||
SAVED_CODE: ['saveCode', 'runCode'],
|
SAVED_CODE: { do: 'saveCode', to: 'evaluatingCode' },
|
||||||
CHANGED_CODE: { secretlyDo: 'setCode' },
|
CHANGED_CODE: { secretlyDo: 'setCode' },
|
||||||
CLEARED_ERROR: { if: 'hasError', do: 'clearError' },
|
CLEARED_ERROR: { if: 'hasError', do: 'clearError' },
|
||||||
TOGGLED_DOCS: { to: 'viewingDocs' },
|
TOGGLED_DOCS: { to: 'viewingDocs' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
evaluatingCode: {
|
||||||
|
async: {
|
||||||
|
await: 'evalCode',
|
||||||
|
onResolve: {
|
||||||
|
do: ['clearError', 'sendResultToGlobalState'],
|
||||||
|
to: 'editingCode',
|
||||||
|
},
|
||||||
|
onReject: { do: 'setErrorFromResult', to: 'editingCode' },
|
||||||
|
},
|
||||||
|
},
|
||||||
viewingDocs: {
|
viewingDocs: {
|
||||||
on: {
|
on: {
|
||||||
TOGGLED_DOCS: { to: 'editingCode' },
|
TOGGLED_DOCS: { to: 'editingCode' },
|
||||||
|
@ -78,22 +81,6 @@ export default function CodePanel(): JSX.Element {
|
||||||
setCode(data, payload: { code: string }) {
|
setCode(data, payload: { code: string }) {
|
||||||
data.code = payload.code
|
data.code = payload.code
|
||||||
},
|
},
|
||||||
runCode(data) {
|
|
||||||
let error = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
generateFromCode(state.data, data.code).then(
|
|
||||||
({ shapes, controls }) => {
|
|
||||||
state.send('GENERATED_FROM_CODE', { shapes, controls })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Got an error!', e)
|
|
||||||
error = { message: e.message, ...getErrorLineAndColumn(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
data.error = error
|
|
||||||
},
|
|
||||||
saveCode(data) {
|
saveCode(data) {
|
||||||
const { code } = data
|
const { code } = data
|
||||||
state.send('SAVED_CODE', { code })
|
state.send('SAVED_CODE', { code })
|
||||||
|
@ -101,6 +88,25 @@ export default function CodePanel(): JSX.Element {
|
||||||
clearError(data) {
|
clearError(data) {
|
||||||
data.error = null
|
data.error = null
|
||||||
},
|
},
|
||||||
|
setErrorFromResult(data, payload, result: CodeResult) {
|
||||||
|
data.error = result.error
|
||||||
|
},
|
||||||
|
sendResultToGlobalState(data, payload, result: CodeResult) {
|
||||||
|
state.send('GENERATED_FROM_CODE', result)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
asyncs: {
|
||||||
|
evalCode(data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
generateFromCode(state.data, data.code).then((result) => {
|
||||||
|
if (result.error !== null) {
|
||||||
|
reject(result)
|
||||||
|
} else {
|
||||||
|
resolve(result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -115,6 +121,17 @@ export default function CodePanel(): JSX.Element {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleCodeChange = useCallback(
|
||||||
|
(code: string) => local.send('CHANGED_CODE', { code }),
|
||||||
|
[local]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => local.send('SAVED_CODE'), [local])
|
||||||
|
|
||||||
|
const handleKey = useCallback(() => local.send('CLEARED_ERROR'), [local])
|
||||||
|
|
||||||
|
const toggleDocs = useCallback(() => local.send('TOGGLED_DOCS'), [local])
|
||||||
|
|
||||||
const { error } = local.data
|
const { error } = local.data
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -131,7 +148,7 @@ export default function CodePanel(): JSX.Element {
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
|
onClick={toggleCodePanel}
|
||||||
>
|
>
|
||||||
<X />
|
<X />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -142,14 +159,14 @@ export default function CodePanel(): JSX.Element {
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size="small"
|
size="small"
|
||||||
disabled={!local.isIn('editingCode')}
|
disabled={!local.isIn('editingCode')}
|
||||||
onClick={() => state.send('INCREASED_CODE_FONT_SIZE')}
|
onClick={increaseCodeSize}
|
||||||
>
|
>
|
||||||
<ChevronUp />
|
<ChevronUp />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
disabled={!local.isIn('editingCode')}
|
disabled={!local.isIn('editingCode')}
|
||||||
onClick={() => state.send('DECREASED_CODE_FONT_SIZE')}
|
onClick={decreaseCodeSize}
|
||||||
>
|
>
|
||||||
<ChevronDown />
|
<ChevronDown />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -157,7 +174,7 @@ export default function CodePanel(): JSX.Element {
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => local.send('TOGGLED_DOCS')}
|
onClick={toggleDocs}
|
||||||
>
|
>
|
||||||
<Info />
|
<Info />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -165,7 +182,7 @@ export default function CodePanel(): JSX.Element {
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size="small"
|
size="small"
|
||||||
disabled={!local.isIn('editingCode')}
|
disabled={!local.isIn('editingCode')}
|
||||||
onClick={() => local.send('SAVED_CODE')}
|
onClick={handleSave}
|
||||||
>
|
>
|
||||||
<PlayCircle />
|
<PlayCircle />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -177,24 +194,20 @@ export default function CodePanel(): JSX.Element {
|
||||||
readOnly={isReadOnly}
|
readOnly={isReadOnly}
|
||||||
value={file.code}
|
value={file.code}
|
||||||
error={error}
|
error={error}
|
||||||
onChange={(code) => local.send('CHANGED_CODE', { code })}
|
onChange={handleCodeChange}
|
||||||
onSave={() => local.send('SAVED_CODE')}
|
onSave={handleSave}
|
||||||
onKey={() => local.send('CLEARED_ERROR')}
|
onKey={handleKey}
|
||||||
/>
|
/>
|
||||||
<CodeDocs isHidden={!local.isIn('viewingDocs')} />
|
<CodeDocs isHidden={!local.isIn('viewingDocs')} />
|
||||||
</Panel.Content>
|
</Panel.Content>
|
||||||
<Panel.Footer>
|
|
||||||
{error &&
|
{error && <Panel.Footer>{error.message}</Panel.Footer>}
|
||||||
(error.line
|
|
||||||
? `(${Number(error.line) - 2}:${error.column}) ${error.message}`
|
|
||||||
: error.message)}
|
|
||||||
</Panel.Footer>
|
|
||||||
</Panel.Layout>
|
</Panel.Layout>
|
||||||
) : (
|
) : (
|
||||||
<IconButton
|
<IconButton
|
||||||
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
bp={{ '@initial': 'mobile', '@sm': 'small' }}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => state.send('TOGGLED_CODE_PANEL_OPEN')}
|
onClick={toggleCodePanel}
|
||||||
>
|
>
|
||||||
<Code />
|
<Code />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
@ -19,9 +19,11 @@ import {
|
||||||
ColorStyle,
|
ColorStyle,
|
||||||
FontSize,
|
FontSize,
|
||||||
SizeStyle,
|
SizeStyle,
|
||||||
|
CodeError,
|
||||||
} from 'types'
|
} from 'types'
|
||||||
import { getPage, getShapes } from 'utils'
|
import { getPage, getShapes } from 'utils'
|
||||||
import { transform } from 'sucrase'
|
import { transform } from 'sucrase'
|
||||||
|
import { getErrorWithLineAndColumn, getFormattedCode } from 'utils/code'
|
||||||
|
|
||||||
const baseScope = {
|
const baseScope = {
|
||||||
Dot,
|
Dot,
|
||||||
|
@ -54,6 +56,7 @@ export async function generateFromCode(
|
||||||
): Promise<{
|
): Promise<{
|
||||||
shapes: Shape[]
|
shapes: Shape[]
|
||||||
controls: CodeControl[]
|
controls: CodeControl[]
|
||||||
|
error: CodeError
|
||||||
}> {
|
}> {
|
||||||
codeControls.clear()
|
codeControls.clear()
|
||||||
codeShapes.clear()
|
codeShapes.clear()
|
||||||
|
@ -63,18 +66,27 @@ export async function generateFromCode(
|
||||||
const { currentPageId } = data
|
const { currentPageId } = data
|
||||||
const scope = { ...baseScope, controls, currentPageId }
|
const scope = { ...baseScope, controls, currentPageId }
|
||||||
|
|
||||||
const transformed = transform(code, {
|
let generatedShapes: Shape[] = []
|
||||||
transforms: ['typescript'],
|
let generatedControls: CodeControl[] = []
|
||||||
}).code
|
let error: CodeError | null = null
|
||||||
|
|
||||||
new Function(...Object.keys(scope), `${transformed}`)(...Object.values(scope))
|
try {
|
||||||
|
const formattedCode = getFormattedCode(code)
|
||||||
|
|
||||||
|
const transformedCode = transform(formattedCode, {
|
||||||
|
transforms: ['typescript'],
|
||||||
|
})?.code
|
||||||
|
|
||||||
|
new Function(...Object.keys(scope), `${transformedCode}`)(
|
||||||
|
...Object.values(scope)
|
||||||
|
)
|
||||||
|
|
||||||
const startingChildIndex =
|
const startingChildIndex =
|
||||||
getShapes(data)
|
getShapes(data)
|
||||||
.filter((shape) => shape.parentId === data.currentPageId)
|
.filter((shape) => shape.parentId === data.currentPageId)
|
||||||
.sort((a, b) => a.childIndex - b.childIndex)[0]?.childIndex || 1
|
.sort((a, b) => a.childIndex - b.childIndex)[0]?.childIndex || 1
|
||||||
|
|
||||||
const generatedShapes = Array.from(codeShapes.values())
|
generatedShapes = Array.from(codeShapes.values())
|
||||||
.sort((a, b) => a.shape.childIndex - b.shape.childIndex)
|
.sort((a, b) => a.shape.childIndex - b.shape.childIndex)
|
||||||
.map((instance, i) => ({
|
.map((instance, i) => ({
|
||||||
...instance.shape,
|
...instance.shape,
|
||||||
|
@ -83,9 +95,12 @@ export async function generateFromCode(
|
||||||
childIndex: startingChildIndex + i,
|
childIndex: startingChildIndex + i,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const generatedControls = Array.from(codeControls.values())
|
generatedControls = Array.from(codeControls.values())
|
||||||
|
} catch (e) {
|
||||||
|
error = getErrorWithLineAndColumn(e)
|
||||||
|
}
|
||||||
|
|
||||||
return { shapes: generatedShapes, controls: generatedControls }
|
return { shapes: generatedShapes, controls: generatedControls, error }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Recursive:wght@500;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Recursive:wght@500;700&display=swap');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Recursive:wght,MONO@420,1&display=swap');
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Verveine Regular';
|
font-family: 'Verveine Regular';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: local('Verveine Regular'), url('/VerveineRegular.woff') format('woff');
|
src: local('Verveine Regular'), url('/VerveineRegular.woff') format('woff');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.margin {
|
||||||
|
user-select: none;
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ const { styled, global, css, theme, getCssString } = createCss({
|
||||||
muted: '#777',
|
muted: '#777',
|
||||||
input: '#f3f3f3',
|
input: '#f3f3f3',
|
||||||
inputBorder: '#ddd',
|
inputBorder: '#ddd',
|
||||||
|
lineError: 'rgba(255, 0, 0, .1)',
|
||||||
},
|
},
|
||||||
space: {},
|
space: {},
|
||||||
fontSizes: {
|
fontSizes: {
|
||||||
|
|
24
types.ts
24
types.ts
|
@ -227,12 +227,6 @@ export type Shape = Readonly<MutableShape>
|
||||||
|
|
||||||
export type ShapeByType<T extends ShapeType> = Shapes[T]
|
export type ShapeByType<T extends ShapeType> = Shapes[T]
|
||||||
|
|
||||||
export interface CodeFile {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Decoration {
|
export enum Decoration {
|
||||||
Arrow = 'Arrow',
|
Arrow = 'Arrow',
|
||||||
}
|
}
|
||||||
|
@ -249,6 +243,24 @@ export interface ShapeHandle {
|
||||||
point: number[]
|
point: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CodeFile {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodeError {
|
||||||
|
message: string
|
||||||
|
line: number
|
||||||
|
column: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodeResult {
|
||||||
|
shapes: Shape[]
|
||||||
|
controls: CodeControl[]
|
||||||
|
error: CodeError
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
/* Editor UI */
|
/* Editor UI */
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
43
utils/code.ts
Normal file
43
utils/code.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import parserTypeScript from 'prettier/parser-typescript'
|
||||||
|
import { CodeError } from 'types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format code with prettier
|
||||||
|
* @param code
|
||||||
|
*/
|
||||||
|
export function getFormattedCode(code: string): string {
|
||||||
|
return prettier.format(code, {
|
||||||
|
parser: 'typescript',
|
||||||
|
plugins: [parserTypeScript],
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'es5',
|
||||||
|
semi: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get line and column from error.
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
export const getErrorWithLineAndColumn = (e: Error | any): CodeError => {
|
||||||
|
if ('line' in e) {
|
||||||
|
return { message: e.message, line: Number(e.line), column: e.column }
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = e.stack.split('/n')[0].match(/(.*)\(([0-9]+):([0-9]+)/)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return {
|
||||||
|
message: result[1],
|
||||||
|
line: Number(result[2]) + 1,
|
||||||
|
column: result[3],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
message: e.message,
|
||||||
|
line: null,
|
||||||
|
column: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue