mirror of
https://github.com/cupcakearmy/svelte-i18n.git
synced 2024-07-02 11:44:53 +02:00
fix: 🐛 prevent extraction of non-deterministic message ids
✅ Closes: #89
This commit is contained in:
parent
89ffb6dd28
commit
9b6adb6538
|
@ -19,20 +19,13 @@ import { Message } from './types';
|
||||||
const LIB_NAME = 'svelte-i18n';
|
const LIB_NAME = 'svelte-i18n';
|
||||||
const DEFINE_MESSAGES_METHOD_NAME = 'defineMessages';
|
const DEFINE_MESSAGES_METHOD_NAME = 'defineMessages';
|
||||||
const FORMAT_METHOD_NAMES = new Set(['format', '_', 't']);
|
const FORMAT_METHOD_NAMES = new Set(['format', '_', 't']);
|
||||||
const IGNORED_UTILITIES = new Set(['number', 'date', 'time']);
|
|
||||||
|
|
||||||
function isFormatCall(node: Node, imports: Set<string>) {
|
function isFormatCall(node: Node, imports: Set<string>) {
|
||||||
if (node.type !== 'CallExpression') return false;
|
if (node.type !== 'CallExpression') return false;
|
||||||
|
|
||||||
let identifier: Identifier;
|
let identifier: Identifier;
|
||||||
|
|
||||||
if (
|
if (node.callee.type === 'Identifier') {
|
||||||
node.callee.type === 'MemberExpression' &&
|
|
||||||
node.callee.property.type === 'Identifier' &&
|
|
||||||
!IGNORED_UTILITIES.has(node.callee.property.name)
|
|
||||||
) {
|
|
||||||
identifier = node.callee.object as Identifier;
|
|
||||||
} else if (node.callee.type === 'Identifier') {
|
|
||||||
identifier = node.callee;
|
identifier = node.callee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,23 +143,28 @@ export function collectMessages(markup: string): Message[] {
|
||||||
...definitions.map((definition) => getObjFromExpression(definition)),
|
...definitions.map((definition) => getObjFromExpression(definition)),
|
||||||
...calls.map((call) => {
|
...calls.map((call) => {
|
||||||
const [pathNode, options] = call.arguments;
|
const [pathNode, options] = call.arguments;
|
||||||
|
let messageObj;
|
||||||
|
|
||||||
if (pathNode.type === 'ObjectExpression') {
|
if (pathNode.type === 'ObjectExpression') {
|
||||||
return getObjFromExpression(pathNode);
|
// _({ ...opts })
|
||||||
|
messageObj = getObjFromExpression(pathNode);
|
||||||
|
} else {
|
||||||
|
const node = pathNode as Literal;
|
||||||
|
const id = node.value as string;
|
||||||
|
|
||||||
|
if (options && options.type === 'ObjectExpression') {
|
||||||
|
// _(id, { ...opts })
|
||||||
|
messageObj = getObjFromExpression(options);
|
||||||
|
messageObj.id = id;
|
||||||
|
} else {
|
||||||
|
// _(id)
|
||||||
|
messageObj = { id };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = pathNode as Literal;
|
if (messageObj?.id == null) return null;
|
||||||
const id = node.value as string;
|
|
||||||
|
|
||||||
if (options && options.type === 'ObjectExpression') {
|
return messageObj;
|
||||||
const messageObj = getObjFromExpression(options);
|
|
||||||
|
|
||||||
messageObj.meta.id = id;
|
|
||||||
|
|
||||||
return messageObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { node, meta: { id } };
|
|
||||||
}),
|
}),
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}
|
}
|
||||||
|
@ -175,28 +173,28 @@ export function extractMessages(
|
||||||
markup: string,
|
markup: string,
|
||||||
{ accumulator = {}, shallow = false, overwrite = false } = {} as any,
|
{ accumulator = {}, shallow = false, overwrite = false } = {} as any,
|
||||||
) {
|
) {
|
||||||
collectMessages(markup).forEach((message) => {
|
collectMessages(markup).forEach((messageObj) => {
|
||||||
let defaultValue = message.meta.default;
|
let defaultValue = messageObj.default;
|
||||||
|
|
||||||
if (typeof defaultValue === 'undefined') {
|
if (typeof defaultValue === 'undefined') {
|
||||||
defaultValue = '';
|
defaultValue = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shallow) {
|
if (shallow) {
|
||||||
if (overwrite === false && message.meta.id in accumulator) {
|
if (overwrite === false && messageObj.id in accumulator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator[message.meta.id] = defaultValue;
|
accumulator[messageObj.id] = defaultValue;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
overwrite === false &&
|
overwrite === false &&
|
||||||
typeof dlv(accumulator, message.meta.id) !== 'undefined'
|
typeof dlv(accumulator, messageObj.id) !== 'undefined'
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
deepSet(accumulator, message.meta.id, defaultValue);
|
deepSet(accumulator, messageObj.id, defaultValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,20 +3,17 @@ import { ObjectExpression, Property, Identifier } from 'estree';
|
||||||
import { Message } from '../types';
|
import { Message } from '../types';
|
||||||
|
|
||||||
export function getObjFromExpression(exprNode: ObjectExpression) {
|
export function getObjFromExpression(exprNode: ObjectExpression) {
|
||||||
return exprNode.properties.reduce<Message>(
|
return exprNode.properties.reduce<Message>((acc, prop: Property) => {
|
||||||
(acc, prop: Property) => {
|
// we only want primitives
|
||||||
// we only want primitives
|
if (
|
||||||
if (
|
prop.value.type === 'Literal' &&
|
||||||
prop.value.type === 'Literal' &&
|
prop.value.value !== Object(prop.value.value)
|
||||||
prop.value.value !== Object(prop.value.value)
|
) {
|
||||||
) {
|
const key = (prop.key as Identifier).name as string;
|
||||||
const key = (prop.key as Identifier).name as string;
|
|
||||||
|
|
||||||
acc.meta[key] = prop.value.value;
|
acc[key] = prop.value.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
}, {});
|
||||||
{ node: exprNode, meta: {} },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import { Node } from 'estree';
|
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
node: Node;
|
id?: string;
|
||||||
meta: {
|
default?: string;
|
||||||
id?: string;
|
[key: string]: any;
|
||||||
default?: string;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// TODO: better tests. these are way too generic.
|
|
||||||
|
|
||||||
import { parse } from 'svelte/compiler';
|
import { parse } from 'svelte/compiler';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -124,7 +122,7 @@ describe('collecting messages', () => {
|
||||||
import { _, defineMessages } from 'svelte-i18n';
|
import { _, defineMessages } from 'svelte-i18n';
|
||||||
|
|
||||||
console.log($_({ id: 'foo' }))
|
console.log($_({ id: 'foo' }))
|
||||||
console.log($_.title({ id: 'page.title' }))
|
console.log($_({ id: 'page.title' }))
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
enabled: { id: 'enabled' , default: 'Enabled' },
|
enabled: { id: 'enabled' , default: 'Enabled' },
|
||||||
|
@ -141,20 +139,36 @@ describe('collecting messages', () => {
|
||||||
expect(messages).toHaveLength(7);
|
expect(messages).toHaveLength(7);
|
||||||
expect(messages).toEqual(
|
expect(messages).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({ meta: { id: 'foo' } }),
|
expect.objectContaining({ id: 'foo' }),
|
||||||
expect.objectContaining({ meta: { id: 'msg_1' } }),
|
expect.objectContaining({ id: 'msg_1' }),
|
||||||
expect.objectContaining({ meta: { id: 'msg_2' } }),
|
expect.objectContaining({ id: 'msg_2' }),
|
||||||
expect.objectContaining({ meta: { id: 'msg_3', default: 'Message' } }),
|
expect.objectContaining({ id: 'msg_3', default: 'Message' }),
|
||||||
expect.objectContaining({ meta: { id: 'page.title' } }),
|
expect.objectContaining({ id: 'page.title' }),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
meta: { id: 'disabled', default: 'Disabled' },
|
id: 'disabled',
|
||||||
|
default: 'Disabled',
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
meta: { id: 'enabled', default: 'Enabled' },
|
id: 'enabled',
|
||||||
|
default: 'Enabled',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignores non-static message ids', () => {
|
||||||
|
const markup = `
|
||||||
|
<script>
|
||||||
|
import { _, defineMessages } from 'svelte-i18n';
|
||||||
|
|
||||||
|
$_({ id: 'foo' + i })
|
||||||
|
$_('bar' + i)
|
||||||
|
</script>`;
|
||||||
|
|
||||||
|
const messages = collectMessages(markup);
|
||||||
|
|
||||||
|
expect(messages).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('messages extraction', () => {
|
describe('messages extraction', () => {
|
||||||
|
@ -163,7 +177,7 @@ describe('messages extraction', () => {
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1>{$_.title('title')}</h1>
|
<h1>{$_('title')}</h1>
|
||||||
<h2>{$_({ id: 'subtitle'})}</h2>
|
<h2>{$_({ id: 'subtitle'})}</h2>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -176,7 +190,7 @@ describe('messages extraction', () => {
|
||||||
const markup = `
|
const markup = `
|
||||||
<script>import { _ } from 'svelte-i18n';</script>
|
<script>import { _ } from 'svelte-i18n';</script>
|
||||||
|
|
||||||
<h1>{$_.title('home.page.title')}</h1>
|
<h1>{$_('home.page.title')}</h1>
|
||||||
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{$_('list.0')}</li>
|
<li>{$_('list.0')}</li>
|
||||||
|
@ -197,7 +211,7 @@ describe('messages extraction', () => {
|
||||||
const markup = `
|
const markup = `
|
||||||
<script>import { _ } from 'svelte-i18n';</script>
|
<script>import { _ } from 'svelte-i18n';</script>
|
||||||
|
|
||||||
<h1>{$_.title('home.page.title')}</h1>
|
<h1>{$_('home.page.title')}</h1>
|
||||||
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{$_('list.0')}</li>
|
<li>{$_('list.0')}</li>
|
||||||
|
@ -221,7 +235,7 @@ describe('messages extraction', () => {
|
||||||
const markup = `
|
const markup = `
|
||||||
<script>import { _ } from 'svelte-i18n';</script>
|
<script>import { _ } from 'svelte-i18n';</script>
|
||||||
|
|
||||||
<h1>{$_.title('home.page.title')}</h1>
|
<h1>{$_('home.page.title')}</h1>
|
||||||
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{$_('list.0')}</li>
|
<li>{$_('list.0')}</li>
|
||||||
|
@ -256,7 +270,7 @@ describe('messages extraction', () => {
|
||||||
const markup = `
|
const markup = `
|
||||||
<script>import { _ } from 'svelte-i18n';</script>
|
<script>import { _ } from 'svelte-i18n';</script>
|
||||||
|
|
||||||
<h1>{$_.title('home.page.title')}</h1>
|
<h1>{$_('home.page.title')}</h1>
|
||||||
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
<h2>{$_({ id: 'home.page.subtitle'})}</h2>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user