Skip to content

Commit 52891d0

Browse files
authored
Report fluent syntax errors (#22)
1 parent 01eab6f commit 52891d0

File tree

13 files changed

+170
-71
lines changed

13 files changed

+170
-71
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<div>
3+
<div>{{ $t('hello-user', { userName }) }}</div>
4+
<div>{{ $t('shared-photos', { userName, photoCount, userGender }) }}</div>
5+
</div>
6+
</template>

__tests__/fixtures/errors.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<template>
2+
<div>
3+
<div>{{ $t('hello-user', { userName }) }}</div>
4+
<div>{{ $t('shared-photos', { userName, photoCount, userGender }) }}</div>
5+
</div>
6+
</template>
7+
8+
<fluent locale="en">
9+
# Simple things are simple.
10+
hello-user = Hello, {$userName!
11+
12+
# Complex things are possible.
13+
shared-photos =
14+
{$userName} {$photoCount ->
15+
[one] added one photo
16+
[other] added {$photoCount} new photo
17+
}to {$userGender ->
18+
[male] his stream
19+
[female] her stream
20+
*[other] their stream
21+
}.
22+
</fluent>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Simple things are simple.
2+
hello-user = Hello, {$userName!
3+
4+
# Complex things are possible.
5+
shared-photos =
6+
{$userName} {$photoCount ->
7+
[one] added one photo
8+
[other] added {$photoCount} new photo
9+
}to {$userGender ->
10+
[male] his stream
11+
[female] her stream
12+
*[other] their stream
13+
}.

__tests__/frameworks/vite/__snapshots__/external.spec.ts.snap

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ exports[`Vite external > works 1`] = `
55
import { createHotContext as __vite__createHotContext } from \\"/@vite/client\\";import.meta.hot = __vite__createHotContext(\\"/fixtures/components/external.vue\\");import da_ftl from '/fixtures/ftl/da/components/external.vue.ftl?import';
66
import en_ftl from '/fixtures/ftl/en/components/external.vue.ftl?import';
77
const _sfc_main = {}
8-
const _toDisplayString = window['Vue'].toDisplayString
9-
const _createElementVNode = window['Vue'].createElementVNode
10-
const _openBlock = window['Vue'].openBlock
11-
const _createElementBlock = window['Vue'].createElementBlock
12-
8+
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"/@id/virtual:empty:vue\\"
139
1410
function _sfc_render(_ctx, _cache) {
1511
return (_openBlock(), _createElementBlock(\\"div\\", null, [
@@ -57,13 +53,13 @@ export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__f
5753
5854
=== /fixtures/ftl/da/components/external.vue.ftl ===
5955
60-
import { FluentResource } from '.vite-plugin-externals/@fluent/bundle.js'
56+
import { FluentResource } from '/@id/virtual:empty:fluent-bundle'
6157
export default new FluentResource(\\"\\")
6258
6359
6460
=== /fixtures/ftl/en/components/external.vue.ftl ===
6561
66-
import { FluentResource } from '.vite-plugin-externals/@fluent/bundle.js'
62+
import { FluentResource } from '/@id/virtual:empty:fluent-bundle'
6763
export default new FluentResource(\\"# Simple things are simple.\\\\nhello-user = Hello, {$userName}!\\\\n\\\\n# Complex things are possible.\\\\nshared-photos =\\\\n {$userName} {$photoCount ->\\\\n [one] added one photo\\\\n *[other] added {$photoCount} new photo\\\\n }to {$userGender ->\\\\n [male] his stream\\\\n [female] her stream\\\\n *[other] their stream\\\\n }.\\\\n\\")
6864
"
6965
`;
@@ -84,11 +80,7 @@ return __returned__
8480
}
8581
8682
}
87-
const _toDisplayString = window['Vue'].toDisplayString
88-
const _createElementVNode = window['Vue'].createElementVNode
89-
const _openBlock = window['Vue'].openBlock
90-
const _createElementBlock = window['Vue'].createElementBlock
91-
83+
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"/@id/virtual:empty:vue\\"
9284
9385
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
9486
return (_openBlock(), _createElementBlock(\\"div\\", null, [
@@ -134,7 +126,7 @@ export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__f
134126
135127
=== /fixtures/ftl/en/components/external.setup.vue.ftl ===
136128
137-
import { FluentResource } from '.vite-plugin-externals/@fluent/bundle.js'
129+
import { FluentResource } from '/@id/virtual:empty:fluent-bundle'
138130
export default new FluentResource(\\"# Simple things are simple.\\\\nhello-user = Hello, {$userName}!\\\\n\\\\n# Complex things are possible.\\\\nshared-photos =\\\\n {$userName} {$photoCount ->\\\\n [one] added one photo\\\\n *[other] added {$photoCount} new photo\\\\n } to {$userGender ->\\\\n [male] his stream\\\\n [female] her stream\\\\n *[other] their stream\\\\n }.\\\\n\\")
139131
"
140132
`;

__tests__/frameworks/vite/__snapshots__/sfc.spec.ts.snap

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
exports[`Vite SFC > generates custom block code 1`] = `
44
"=== /fixtures/test.vue ===
55
import { createHotContext as __vite__createHotContext } from \\"/@vite/client\\";import.meta.hot = __vite__createHotContext(\\"/fixtures/test.vue\\");const _sfc_main = {}
6-
const _toDisplayString = window['Vue'].toDisplayString
7-
const _createElementVNode = window['Vue'].createElementVNode
8-
const _openBlock = window['Vue'].openBlock
9-
const _createElementBlock = window['Vue'].createElementBlock
10-
6+
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"/@id/virtual:empty:vue\\"
117
128
function _sfc_render(_ctx, _cache) {
139
return (_openBlock(), _createElementBlock(\\"div\\", null, [
@@ -35,8 +31,7 @@ export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__f
3531
3632
=== /fixtures/test.vue?vue&type=fluent&index=0&locale=en&lang.fluent ===
3733
38-
const FluentResource = window['FluentBundle'].FluentResource
39-
34+
import { FluentResource } from '/@id/virtual:empty:fluent-bundle'
4035
4136
export default function (Component) {
4237
const target = Component.options || Component
@@ -49,8 +44,7 @@ export default function (Component) {
4944
exports[`Vite SFC > supports custom blockType 1`] = `
5045
"=== /fixtures/blockType.vue ===
5146
import { createHotContext as __vite__createHotContext } from \\"/@vite/client\\";import.meta.hot = __vite__createHotContext(\\"/fixtures/blockType.vue\\");const _sfc_main = {}
52-
const _toDisplayString = window['Vue'].toDisplayString
53-
47+
import { toDisplayString as _toDisplayString } from \\"/@id/virtual:empty:vue\\"
5448
5549
function _sfc_render(_ctx, _cache) {
5650
return _toDisplayString(_ctx.$t('test-key'))
@@ -75,8 +69,7 @@ export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__f
7569
7670
=== /fixtures/blockType.vue?vue&type=i18n&index=0&locale=en&lang.i18n ===
7771
78-
const FluentResource = window['FluentBundle'].FluentResource
79-
72+
import { FluentResource } from '/@id/virtual:empty:fluent-bundle'
8073
8174
export default function (Component) {
8275
const target = Component.options || Component
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { resolve } from 'path'
2+
import { describe, expect, it } from 'vitest'
3+
4+
import vue3base from '@vitejs/plugin-vue'
5+
import compiler from '@vue/compiler-sfc'
6+
7+
import { ExternalFluentPlugin, SFCFluentPlugin } from '../../../src/vite'
8+
import { compile } from './util'
9+
10+
const vue3 = () => vue3base({
11+
compiler,
12+
})
13+
14+
const baseDir = resolve(__dirname, '../..')
15+
16+
describe('Error checking', () => {
17+
it('checks for syntax errors in external ftl files', async () => {
18+
// Arrange
19+
// Act
20+
const code = compile({
21+
plugins: [
22+
vue3(),
23+
ExternalFluentPlugin({
24+
baseDir: resolve(baseDir, 'fixtures'),
25+
ftlDir: resolve(baseDir, 'fixtures/ftl'),
26+
locales: ['en', 'da'],
27+
checkSyntax: true,
28+
}),
29+
],
30+
}, '/fixtures/components/errors.vue')
31+
32+
// Assert
33+
await expect(code).rejects.toThrowErrorMatchingInlineSnapshot(`
34+
"Fluent parse errors:
35+
E0003: Expected token: \\"}\\" (2:31)
36+
E0010: Expected one of the variants to be marked as default (*) (9:3)"
37+
`)
38+
})
39+
40+
it('checks for syntax errors in custom blocks', async () => {
41+
// Arrange
42+
// Act
43+
const code = compile({
44+
plugins: [
45+
vue3(),
46+
SFCFluentPlugin({
47+
checkSyntax: true,
48+
}),
49+
],
50+
}, '/fixtures/errors.vue')
51+
52+
// Assert
53+
await expect(code).rejects.toThrowErrorMatchingInlineSnapshot(`
54+
"Fluent parse errors:
55+
E0003: Expected token: \\"}\\" (2:31)
56+
E0010: Expected one of the variants to be marked as default (*) (9:3)"
57+
`)
58+
})
59+
})

__tests__/frameworks/vite/util.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { resolve, sep } from 'path'
33
import type { InlineConfig, ModuleNode } from 'vite'
44
import { createServer } from 'vite'
55

6-
import { viteExternalsPlugin } from 'vite-plugin-externals'
7-
86
const baseDir = resolve(__dirname, '../..')
97

108
export async function compile(options: InlineConfig, file: string): Promise<string | undefined> {
@@ -13,10 +11,21 @@ export async function compile(options: InlineConfig, file: string): Promise<stri
1311
...options,
1412
plugins: [
1513
...options.plugins,
16-
viteExternalsPlugin({
17-
'vue': 'Vue',
18-
'@fluent/bundle': 'FluentBundle',
19-
}),
14+
{
15+
name: 'externals',
16+
config(config) {
17+
const newAlias = [
18+
...(config?.resolve?.alias ?? []),
19+
{ find: /^vue$/, replacement: 'virtual:empty:vue' },
20+
{ find: /^@fluent\/bundle$/, replacement: 'virtual:empty:fluent-bundle' },
21+
]
22+
23+
config.resolve = {
24+
...(config.resolve ?? {}),
25+
alias: newAlias,
26+
}
27+
},
28+
},
2029
],
2130
})
2231

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"prepare": "husky install"
6161
},
6262
"dependencies": {
63+
"@fluent/syntax": "^0.18.1",
6364
"@rollup/pluginutils": "^4.2.1",
6465
"magic-string": "^0.26.2",
6566
"unplugin": "^0.9.0"
@@ -80,7 +81,6 @@
8081
"tsup": "6.2.3",
8182
"typescript": "4.8.2",
8283
"vite": "3.0.9",
83-
"vite-plugin-externals": "0.5.1",
8484
"vitest": "0.22.1",
8585
"vue": "3.2.38",
8686
"vue-loader": "17.0.0",

pnpm-lock.yaml

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

src/plugins/external-plugin.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import MagicString from 'magic-string'
66
import { createFilter, makeLegalIdentifier } from '@rollup/pluginutils'
77

88
import type { ExternalPluginOptions, InsertInfo } from '../types'
9+
import { getSyntaxErrors } from './ftl/parse'
910

1011
function getInsertInfo(source: string): InsertInfo {
1112
let target = null
@@ -58,6 +59,8 @@ interface Dependency {
5859
}
5960

6061
export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => {
62+
options.checkSyntax = options.checkSyntax ?? false
63+
6164
return {
6265
name: 'unplugin-fluent-vue-external',
6366
enforce: meta.framework === 'webpack' ? 'post' : undefined,
@@ -120,6 +123,12 @@ if (__HOT_API__) {
120123
}
121124

122125
if (isFtl(id)) {
126+
if (options.checkSyntax) {
127+
const errorsText = getSyntaxErrors(source)
128+
if (errorsText)
129+
this.error(errorsText)
130+
}
131+
123132
return `
124133
import { FluentResource } from '@fluent/bundle'
125134
export default new FluentResource(${JSON.stringify(source)})

0 commit comments

Comments
 (0)