Skip to content

Commit 160deb8

Browse files
committed
feat: add colors to parser error output
1 parent 6d88a14 commit 160deb8

File tree

4 files changed

+42
-70
lines changed

4 files changed

+42
-70
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"lilconfig": "^2.1.0",
6060
"magic-string": "^0.30.0",
6161
"mlly": "^1.0.0",
62+
"picocolors": "^1.0.0",
6263
"pkg-dir": "^7.0.0",
6364
"serialize-error": "^11.0.0",
6465
"superjson": "^1.13.1",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/transform/formatParserErrors.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ParseErrorSpecification } from '../core/types'
2+
import pico from 'picocolors'
23

34
const getNumDigits = (number: number) => (Math.log(number) * Math.LOG10E + 1) | 0
45

@@ -16,12 +17,12 @@ export function formatBabelParseError(
1617

1718
// Format the error message.
1819
const topLine = [
19-
`babel parse error: ${errorName}`,
20+
`🐛 babel parse error: ${errorName}`,
2021
error.syntaxPlugin !== undefined && `syntax plugin: ${error.syntaxPlugin}`,
2122
error.missingPlugin !== undefined && `missing plugin(s): "${error.missingPlugin}"`,
22-
`---> ${fileNamePath === undefined ? 'unspecified file' : fileNamePath}:${error.loc.line}:${
23-
error.loc.column
24-
}`
23+
`${pico.green('--->')} ${fileNamePath === undefined ? 'unspecified file' : fileNamePath}:${
24+
error.loc.line
25+
}:${error.loc.column}`
2526
]
2627
.filter(Boolean)
2728
.join('\n')
@@ -30,13 +31,13 @@ export function formatBabelParseError(
3031

3132
const pointerLine =
3233
' '.repeat(getNumDigits(error.loc.line) + error.loc.column - getNumDigits(error.loc.line)) +
33-
'^'.repeat(errorUnderlineLength) +
34+
pico.red('^').repeat(errorUnderlineLength) +
3435
' ' +
35-
errorMessage
36+
pico.red(errorMessage)
3637
const codeDisplay = [
37-
' '.repeat(getNumDigits(error.loc.line) + 1) + '|',
38-
`${error.loc.line} | ${errorLine}`,
39-
`${' '.repeat(getNumDigits(error.loc.line) + 1) + '|'} ${pointerLine}`
38+
' '.repeat(getNumDigits(error.loc.line) + 1) + pico.green('|'),
39+
`${pico.green(error.loc.line)} ${pico.green('|')} ${errorLine}`,
40+
`${' '.repeat(getNumDigits(error.loc.line) + 1) + pico.green('|')} ${pointerLine}`
4041
].join('\n')
4142

4243
return `${topLine}\n${codeDisplay}\n`

test/parsing-errors.test.ts

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it } from 'vitest'
1+
import { describe, it } from 'vitest'
22
import { createHumanLog } from '../src/core/errors'
33
import { parse as babelParse } from '@babel/parser'
44
import { getParseConfig } from '../src/transform/config'
@@ -36,85 +36,37 @@ export const CapturedCall = () => {
3636

3737
type ParsingErrorTest = {
3838
fixture: string
39-
getErrorMessage: (fixture: string) => string
40-
}
41-
42-
const createParsingError = (
43-
code: string,
44-
fileNamePath: string,
45-
lineNumber: number,
46-
column: number,
47-
errorMessage: string
48-
) => {
49-
const parsingErrorMessage = formatBabelParseError(
50-
{
51-
name: 'SyntaxError',
52-
message: errorMessage,
53-
loc: {
54-
line: lineNumber,
55-
column: column,
56-
index: -1
57-
},
58-
code: 'BABEL_PARSER_SYNTAX_ERROR',
59-
reasonCode: 'some reason code',
60-
details: {}
61-
},
62-
code,
63-
fileNamePath
64-
)
65-
66-
return createHumanLog({
67-
events: ['parsing_failed'],
68-
explanations: ['parsing_error_explanation'],
69-
solutions: ['parsing_error_open_issue', 'parsing_error_configure_babel_parser_options'],
70-
params: {
71-
fileNamePath,
72-
parsingError: parsingErrorMessage
73-
}
74-
}).toString()
39+
requiredErrorMessageParts: string[]
7540
}
7641

7742
const parsingErrorFixtures: Record<string, ParsingErrorTest[]> = {
7843
'illegal code': [
7944
{
8045
fixture: `function foo() {-}`,
81-
getErrorMessage: (fixture) =>
82-
createParsingError(
83-
fixture,
84-
'/file.js',
85-
1,
86-
fixture.indexOf('-') + 1,
87-
`Unexpected token (1:17)`
88-
)
46+
requiredErrorMessageParts: ['Unexpected token (1:17)']
8947
},
9048
{
9149
fixture: `funct foo() {}`,
92-
getErrorMessage: (fixture) =>
93-
createParsingError(fixture, '/file.js', 1, 5, `Missing semicolon. (1:5)`)
50+
requiredErrorMessageParts: ['Missing semicolon. (1:5)']
9451
},
9552
{
9653
fixture: `function foo#() {}`,
97-
getErrorMessage: (fixture) =>
98-
createParsingError(fixture, '/file.js', 1, 12, `Unexpected token, expected "(" (1:12)`)
54+
requiredErrorMessageParts: ['Unexpected token, expected "(" (1:12)']
9955
},
10056
{
10157
fixture: `const x = 1 ++ 2;`,
102-
getErrorMessage: (fixture) =>
103-
createParsingError(
104-
fixture,
105-
'/file.js',
106-
1,
107-
10,
108-
`Invalid left-hand side in postfix operation. (1:10)`
109-
)
58+
requiredErrorMessageParts: ['Invalid left-hand side in postfix operation. (1:10)']
11059
}
11160
],
11261
// @todo: write more examples
11362
'not enabled babel syntax': [
11463
// throw Expressions
11564
{
11665
fixture: `function save(filename = throw new TypeError("Argument required")) {}`,
117-
getErrorMessage: (fixture) => ''
66+
requiredErrorMessageParts: [
67+
'missing plugin(s): "throwExpressions"',
68+
'This experimental syntax requires enabling the parser plugin: "throwExpressions". (1:25)'
69+
]
11870
},
11971
// do expressions
12072
{
@@ -126,7 +78,10 @@ const parsingErrorFixtures: Record<string, ParsingErrorTest[]> = {
12678
("small");
12779
}
12880
};`,
129-
getErrorMessage: (fixture) => 'todo'
81+
requiredErrorMessageParts: [
82+
'missing plugin(s): "doExpressions"',
83+
'This experimental syntax requires enabling the parser plugin: "doExpressions". (2:11)'
84+
]
13085
}
13186
]
13287
/* 'regression tests': [
@@ -142,8 +97,20 @@ describe('gives human-friendly errors when parsing invalid code', () => {
14297
it(suiteName, () => {
14398
for (let i = 0; i < parseErrorTests.length; i++) {
14499
const parseResponse = parse(parseErrorTests[i].fixture, '/file.js')
145-
const expectedResponse = parseErrorTests[i].getErrorMessage(parseErrorTests[i].fixture)
146-
expect(parseResponse).toEqual(expectedResponse)
100+
// const expectedResponse = parseErrorTests[i].getErrorMessage(parseErrorTests[i].fixture)
101+
102+
parseErrorTests[i].requiredErrorMessageParts.forEach((messagePart) => {
103+
if (!(parseResponse as string).includes(messagePart)) {
104+
console.error('Parse response: ')
105+
console.error(parseResponse)
106+
console.error("Didn't include:", messagePart)
107+
throw new Error('messagePart missing.')
108+
}
109+
// expect((parseResponse as string).includes(messagePart)).toBe(true)
110+
// expect(parseResponse).
111+
})
112+
// expect(parseResponse).toEqual(expectedResponse)
113+
// expect(parseResponse).toContain(expectedResponse)
147114
}
148115
})
149116
}

0 commit comments

Comments
 (0)