From ac610894ffc06da4580d4a7f578c73f5f02c4fa8 Mon Sep 17 00:00:00 2001 From: Ivan Demchuk Date: Wed, 28 Sep 2022 12:05:36 +0300 Subject: [PATCH 1/2] Show codeframe with location of fluent parse error --- __tests__/frameworks/vite/errors.spec.ts | 27 +++++++++++++- src/plugins/ftl/parse.ts | 47 +++++++++++++++++++++++- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/__tests__/frameworks/vite/errors.spec.ts b/__tests__/frameworks/vite/errors.spec.ts index cee406a..70c67c6 100644 --- a/__tests__/frameworks/vite/errors.spec.ts +++ b/__tests__/frameworks/vite/errors.spec.ts @@ -33,7 +33,19 @@ describe('Error checking', () => { await expect(code).rejects.toThrowErrorMatchingInlineSnapshot(` "Fluent parse errors: E0003: Expected token: \\"}\\" (2:31) - E0010: Expected one of the variants to be marked as default (*) (9:3)" + 1 | # Simple things are simple. + 2 | hello-user = Hello, {$userName! + | ^ + 3 | + 4 | # Complex things are possible. + E0010: Expected one of the variants to be marked as default (*) (9:3) + 7 | [one] added one photo + 8 | [other] added {$photoCount} new photo + 9 | }to {$userGender -> + | ^ + 10 | [male] his stream + 11 | [female] her stream + " `) }) @@ -53,7 +65,18 @@ describe('Error checking', () => { await expect(code).rejects.toThrowErrorMatchingInlineSnapshot(` "Fluent parse errors: E0003: Expected token: \\"}\\" (2:31) - E0010: Expected one of the variants to be marked as default (*) (9:3)" + 1 | # Simple things are simple. + 2 | hello-user = Hello, {$userName! + | ^ + 3 | + 4 | # Complex things are possible. + E0010: Expected one of the variants to be marked as default (*) (9:3) + 7 | [one] added one photo + 8 | [other] added {$photoCount} new photo + 9 | }to {$userGender -> + | ^ + 10 | [male] his stream + 11 | [female] her stream" `) }) }) diff --git a/src/plugins/ftl/parse.ts b/src/plugins/ftl/parse.ts index 031710b..4908237 100644 --- a/src/plugins/ftl/parse.ts +++ b/src/plugins/ftl/parse.ts @@ -1,7 +1,49 @@ import type { Junk } from '@fluent/syntax' import { columnOffset, lineOffset, parse } from '@fluent/syntax' -export function getSyntaxErrors(source: string): string | undefined { +function padRight(str: string | number, len: number) { + return str + ' '.repeat(len - String(str).length) +} + +const RANGE = 2 + +/** + * Generate a string that highlights the position of the error in the source + * @param source The source string + * @param line The line number of the error (1-indexed) + * @param column The column number of the error (1-indexed) + * Example: + * | proper-key = Value + * | key-with-error = error {-> + * | ^ + * | continuation = Value + */ +export function generateCodeFrame( + source: string, + line: number, + column: number, +): string { + const lines = source.split('\n') + const start = Math.max(line - RANGE - 1, 0) + const end = Math.min(lines.length, line + RANGE) + + const result = [] + + const lineNumberLength = String(end).length + + for (let i = start; i < end; i++) { + result.push(`${padRight(i + 1, lineNumberLength)} | ${lines[i]}`) + + if (i + 1 === line) + result.push(`${padRight(' ', lineNumberLength)} | ${' '.repeat(column - 1)}^`) + } + + return result.join('\n') +} + +export function getSyntaxErrors( + source: string, +): string | undefined { const parsed = parse(source, { withSpans: true }) const junks = parsed.body.filter(x => x.type === 'Junk') as Junk[] const errors = junks.map(x => x.annotations).flat() @@ -9,7 +51,8 @@ export function getSyntaxErrors(source: string): string | undefined { const errorsText = errors.map((x) => { const line = lineOffset(source, x.span.start) + 1 const column = columnOffset(source, x.span.start) + 1 - return ` ${x.code}: ${x.message} (${line}:${column})` + return ` ${x.code}: ${x.message} (${line}:${column}) +${generateCodeFrame(source, line, column)}` }).join('\n') return `Fluent parse errors:\n${errorsText}` From c4d74b1cde415efd81ad0e6d47dcc7861ae98f6e Mon Sep 17 00:00:00 2001 From: Ivan Demchuk Date: Sat, 1 Oct 2022 09:06:53 +0300 Subject: [PATCH 2/2] Make tests stable across different OS --- __tests__/frameworks/vite/errors.spec.ts | 3 +-- src/plugins/ftl/parse.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/__tests__/frameworks/vite/errors.spec.ts b/__tests__/frameworks/vite/errors.spec.ts index 70c67c6..71f7a30 100644 --- a/__tests__/frameworks/vite/errors.spec.ts +++ b/__tests__/frameworks/vite/errors.spec.ts @@ -44,8 +44,7 @@ describe('Error checking', () => { 9 | }to {$userGender -> | ^ 10 | [male] his stream - 11 | [female] her stream - " + 11 | [female] her stream" `) }) diff --git a/src/plugins/ftl/parse.ts b/src/plugins/ftl/parse.ts index 4908237..91d545e 100644 --- a/src/plugins/ftl/parse.ts +++ b/src/plugins/ftl/parse.ts @@ -23,7 +23,7 @@ export function generateCodeFrame( line: number, column: number, ): string { - const lines = source.split('\n') + const lines = source.split(/\r?\n/) const start = Math.max(line - RANGE - 1, 0) const end = Math.min(lines.length, line + RANGE)