|
1 | 1 | import type { Junk } from '@fluent/syntax' |
2 | 2 | import { columnOffset, lineOffset, parse } from '@fluent/syntax' |
3 | 3 |
|
4 | | -export function getSyntaxErrors(source: string): string | undefined { |
| 4 | +function padRight(str: string | number, len: number) { |
| 5 | + return str + ' '.repeat(len - String(str).length) |
| 6 | +} |
| 7 | + |
| 8 | +const RANGE = 2 |
| 9 | + |
| 10 | +/** |
| 11 | + * Generate a string that highlights the position of the error in the source |
| 12 | + * @param source The source string |
| 13 | + * @param line The line number of the error (1-indexed) |
| 14 | + * @param column The column number of the error (1-indexed) |
| 15 | + * Example: |
| 16 | + * | proper-key = Value |
| 17 | + * | key-with-error = error {-> |
| 18 | + * | ^ |
| 19 | + * | continuation = Value |
| 20 | + */ |
| 21 | +export function generateCodeFrame( |
| 22 | + source: string, |
| 23 | + line: number, |
| 24 | + column: number, |
| 25 | +): string { |
| 26 | + const lines = source.split(/\r?\n/) |
| 27 | + const start = Math.max(line - RANGE - 1, 0) |
| 28 | + const end = Math.min(lines.length, line + RANGE) |
| 29 | + |
| 30 | + const result = [] |
| 31 | + |
| 32 | + const lineNumberLength = String(end).length |
| 33 | + |
| 34 | + for (let i = start; i < end; i++) { |
| 35 | + result.push(`${padRight(i + 1, lineNumberLength)} | ${lines[i]}`) |
| 36 | + |
| 37 | + if (i + 1 === line) |
| 38 | + result.push(`${padRight(' ', lineNumberLength)} | ${' '.repeat(column - 1)}^`) |
| 39 | + } |
| 40 | + |
| 41 | + return result.join('\n') |
| 42 | +} |
| 43 | + |
| 44 | +export function getSyntaxErrors( |
| 45 | + source: string, |
| 46 | +): string | undefined { |
5 | 47 | const parsed = parse(source, { withSpans: true }) |
6 | 48 | const junks = parsed.body.filter(x => x.type === 'Junk') as Junk[] |
7 | 49 | const errors = junks.map(x => x.annotations).flat() |
8 | 50 | if (errors.length > 0) { |
9 | 51 | const errorsText = errors.map((x) => { |
10 | 52 | const line = lineOffset(source, x.span.start) + 1 |
11 | 53 | const column = columnOffset(source, x.span.start) + 1 |
12 | | - return ` ${x.code}: ${x.message} (${line}:${column})` |
| 54 | + return ` ${x.code}: ${x.message} (${line}:${column}) |
| 55 | +${generateCodeFrame(source, line, column)}` |
13 | 56 | }).join('\n') |
14 | 57 |
|
15 | 58 | return `Fluent parse errors:\n${errorsText}` |
|
0 commit comments