Skip to content

Commit

Permalink
feat(@formatjs/icu-messageformat-parser): throw err when trying to fl…
Browse files Browse the repository at this point in the history
…atten a plural inside a tag
  • Loading branch information
longlho committed Feb 20, 2023
1 parent f55cd1d commit dbd5f8f
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 18 deletions.
70 changes: 52 additions & 18 deletions packages/icu-messageformat-parser/manipulator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {
isPluralElement,
isSelectElement,
isTagElement,
MessageFormatElement,
PluralElement,
PluralOrSelectOption,
SelectElement,
} from './types'

function cloneDeep<T>(obj: T): T {
Expand All @@ -21,6 +24,49 @@ function cloneDeep<T>(obj: T): T {
return obj
}

function hoistPluralOrSelectElement(
ast: MessageFormatElement[],
el: PluralElement | SelectElement,
positionToInject: number
) {
// pull this out of the ast and move it to the top
const cloned = cloneDeep(el)
const {options} = cloned
cloned.options = Object.keys(options).reduce(
(all: Record<string, PluralOrSelectOption>, k) => {
const newValue = hoistSelectors([
...ast.slice(0, positionToInject),
...options[k].value,
...ast.slice(positionToInject + 1),
])
all[k] = {
value: newValue,
}
return all
},
{}
)
return cloned
}

function isPluralOrSelectElement(
el: MessageFormatElement
): el is PluralElement | SelectElement {
return isPluralElement(el) || isSelectElement(el)
}

function findPluralOrSelectElement(ast: MessageFormatElement[]): boolean {
return !!ast.find(el => {
if (isPluralOrSelectElement(el)) {
return true
}
if (isTagElement(el)) {
return findPluralOrSelectElement(el.children)
}
return false
})
}

/**
* Hoist all selectors to the beginning of the AST & flatten the
* resulting options. E.g:
Expand All @@ -37,25 +83,13 @@ export function hoistSelectors(
): MessageFormatElement[] {
for (let i = 0; i < ast.length; i++) {
const el = ast[i]
if (isPluralElement(el) || isSelectElement(el)) {
// pull this out of the ast and move it to the top
const cloned = cloneDeep(el)
const {options} = cloned
cloned.options = Object.keys(options).reduce(
(all: Record<string, PluralOrSelectOption>, k) => {
const newValue = hoistSelectors([
...ast.slice(0, i),
...options[k].value,
...ast.slice(i + 1),
])
all[k] = {
value: newValue,
}
return all
},
{}
if (isPluralOrSelectElement(el)) {
return [hoistPluralOrSelectElement(ast, el, i)]
}
if (isTagElement(el) && findPluralOrSelectElement([el])) {
throw new Error(
'Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option'
)
return [cloned]
}
}
return ast
Expand Down
7 changes: 7 additions & 0 deletions packages/icu-messageformat-parser/tests/manipulator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ test('should hoist 1 plural', function () {
)
)
).toBe('{count,plural,one{I have a dog} other{I have many dogs}}')
expect(() =>
hoistSelectors(
parse('I have <b>{count, plural, one{a dog} other{many dogs}}</b>')
)
).toThrowError(
'Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option'
)
})

test('hoist some random case 1', function () {
Expand Down

0 comments on commit dbd5f8f

Please sign in to comment.