-
-
Notifications
You must be signed in to change notification settings - Fork 269
/
source.tsx
151 lines (147 loc) · 3.77 KB
/
source.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Syntax-highlighted source code tag
import { CallSiteLike, parseStack } from '@tapjs/stack'
import { highlightFileSync } from 'prismjs-terminal'
import chalk from 'chalk'
import { Box, Text } from 'ink'
import React, { FC } from 'react'
import stringLength from 'string-length'
export interface SourceOpts {
source?: string
stack?: string
location?: string
at?:
| CallSiteLike
| {
lineNumber?: number
fileName?: string
columnNumber?: number
}
errorOrigin?: {
source?: string
at?:
| CallSiteLike
| {
lineNumber?: number
fileName?: string
columnNumber?: number
}
}
isErrorOrigin?: boolean
}
/**
* Pass in a test result.diag that has a source and callsite,
* and it'll return a prettied up source line with the callsite
* highlighted
*/
export const Source: FC<SourceOpts> = ({
source,
at,
errorOrigin,
stack,
location,
isErrorOrigin = false,
}) => {
if (errorOrigin && typeof errorOrigin === 'object') {
return (
<>
<Source source={source} at={at} />
<Source {...errorOrigin} isErrorOrigin={true} />
</>
)
}
if (location && !stack) {
return (
<Source
{...{
source,
at,
errorOrigin,
stack: location,
isErrorOrigin,
}}
/>
)
}
if (stack && !at) {
if (location) {
return (
<>
<Source stack={location} />
<Source stack={stack} isErrorOrigin={true} />
</>
)
}
const parsed = parseStack(stack)
for (const p of parsed) {
if (p.fileName) {
at = p
break
}
}
}
if (at && at.lineNumber && at.columnNumber && at.fileName) {
try {
const lines: string[] = highlightFileSync(at.fileName, {
lineNumbers: true,
theme: 'moria',
padding: 0,
/* c8 ignore start */
maxWidth:
process.stdout.columns && process.stdout.columns - 5,
/* c8 ignore stop */
}).split('\n')
const lastLine = lines[lines.length - 1]
if (lastLine && stringLength(lastLine) === 0) lines.pop()
const ctx = 4
const startLine = Math.max(at.lineNumber - ctx, 0)
const endLine = Math.min(at.lineNumber + ctx, lines.length)
const numLen = at.lineNumber.toString().length
const maxNumLen = lines.length.toString().length
const excess = maxNumLen - numLen
const atLine = lines[at.lineNumber - 1] as string
const before = lines.slice(startLine, at.lineNumber - 1)
const after = lines.slice(at.lineNumber, endLine)
const len = Math.min(...before.map(l => stringLength(l)))
const msg =
(isErrorOrigin ? 'error origin: ' : '') + at.fileName
const title = chalk.bgAnsi256(234).dim(msg.padEnd(len))
const caret =
at.columnNumber &&
at.columnNumber < stringLength(atLine) &&
at.columnNumber > 0
? chalk.ansi256(252).bgAnsi256(234)(
chalk.red(
' '.repeat(excess) +
'━'.repeat(numLen + at.columnNumber) +
chalk.bold('┛') +
' '.repeat(len - (numLen + at.columnNumber) - 1)
)
)
: ''
const context = [title]
if (!caret) {
context.push(
...before.map(b => ' ' + b),
chalk.bold.red('▶') + atLine,
...after.map(l => ' ' + l)
)
} else {
context.push(...before, atLine, caret, ...after)
}
return (
<Box flexDirection="column">
{context.map((l, key) => (
<Text key={key}>{l}</Text>
))}
</Box>
)
} catch {}
}
return source ? (
<Box>
<Text>{source}</Text>
</Box>
) : (
<></>
)
}