@formatjs/icu-messageformat-parser
Advanced tools
+53
-53
@@ -117,37 +117,37 @@ import { parseDateTimeSkeleton, parseNumberSkeleton, parseNumberSkeletonFromString } from "@formatjs/icu-skeleton-parser"; | ||
| function isLiteralElement(el) { | ||
| return el.type === TYPE.literal; | ||
| return el.type === 0; | ||
| } | ||
| function isArgumentElement(el) { | ||
| return el.type === TYPE.argument; | ||
| return el.type === 1; | ||
| } | ||
| function isNumberElement(el) { | ||
| return el.type === TYPE.number; | ||
| return el.type === 2; | ||
| } | ||
| function isDateElement(el) { | ||
| return el.type === TYPE.date; | ||
| return el.type === 3; | ||
| } | ||
| function isTimeElement(el) { | ||
| return el.type === TYPE.time; | ||
| return el.type === 4; | ||
| } | ||
| function isSelectElement(el) { | ||
| return el.type === TYPE.select; | ||
| return el.type === 5; | ||
| } | ||
| function isPluralElement(el) { | ||
| return el.type === TYPE.plural; | ||
| return el.type === 6; | ||
| } | ||
| function isPoundElement(el) { | ||
| return el.type === TYPE.pound; | ||
| return el.type === 7; | ||
| } | ||
| function isTagElement(el) { | ||
| return el.type === TYPE.tag; | ||
| return el.type === 8; | ||
| } | ||
| function isNumberSkeleton(el) { | ||
| return !!(el && typeof el === "object" && el.type === SKELETON_TYPE.number); | ||
| return !!(el && typeof el === "object" && el.type === 0); | ||
| } | ||
| function isDateTimeSkeleton(el) { | ||
| return !!(el && typeof el === "object" && el.type === SKELETON_TYPE.dateTime); | ||
| return !!(el && typeof el === "object" && el.type === 1); | ||
| } | ||
| function createLiteralElement(value) { | ||
| return { | ||
| type: TYPE.literal, | ||
| type: 0, | ||
| value | ||
@@ -158,3 +158,3 @@ }; | ||
| return { | ||
| type: TYPE.number, | ||
| type: 2, | ||
| value, | ||
@@ -1446,7 +1446,7 @@ style | ||
| elements.push({ | ||
| type: TYPE.pound, | ||
| type: 7, | ||
| location: createLocation(position, this.clonePosition()) | ||
| }); | ||
| } else if (char === 60 && !this.ignoreTag && this.peek() === 47) if (expectingCloseTag) break; | ||
| else return this.error(ErrorKind.UNMATCHED_CLOSING_TAG, createLocation(this.clonePosition(), this.clonePosition())); | ||
| else return this.error(26, createLocation(this.clonePosition(), this.clonePosition())); | ||
| else if (char === 60 && !this.ignoreTag && _isAlpha(this.peek() || 0)) { | ||
@@ -1492,3 +1492,3 @@ const result = this.parseTag(nestingLevel, parentArgType); | ||
| val: { | ||
| type: TYPE.literal, | ||
| type: 0, | ||
| value: `<${tagName}/>`, | ||
@@ -1505,10 +1505,10 @@ location: createLocation(startPosition, this.clonePosition()) | ||
| if (this.bumpIf("</")) { | ||
| if (this.isEOF() || !_isAlpha(this.char())) return this.error(ErrorKind.INVALID_TAG, createLocation(endTagStartPosition, this.clonePosition())); | ||
| if (this.isEOF() || !_isAlpha(this.char())) return this.error(23, createLocation(endTagStartPosition, this.clonePosition())); | ||
| const closingTagNameStartPosition = this.clonePosition(); | ||
| if (tagName !== this.parseTagName()) return this.error(ErrorKind.UNMATCHED_CLOSING_TAG, createLocation(closingTagNameStartPosition, this.clonePosition())); | ||
| if (tagName !== this.parseTagName()) return this.error(26, createLocation(closingTagNameStartPosition, this.clonePosition())); | ||
| this.bumpSpace(); | ||
| if (!this.bumpIf(">")) return this.error(ErrorKind.INVALID_TAG, createLocation(endTagStartPosition, this.clonePosition())); | ||
| if (!this.bumpIf(">")) return this.error(23, createLocation(endTagStartPosition, this.clonePosition())); | ||
| return { | ||
| val: { | ||
| type: TYPE.tag, | ||
| type: 8, | ||
| value: tagName, | ||
@@ -1520,4 +1520,4 @@ children, | ||
| }; | ||
| } else return this.error(ErrorKind.UNCLOSED_TAG, createLocation(startPosition, this.clonePosition())); | ||
| } else return this.error(ErrorKind.INVALID_TAG, createLocation(startPosition, this.clonePosition())); | ||
| } else return this.error(27, createLocation(startPosition, this.clonePosition())); | ||
| } else return this.error(23, createLocation(startPosition, this.clonePosition())); | ||
| } | ||
@@ -1557,3 +1557,3 @@ /** | ||
| val: { | ||
| type: TYPE.literal, | ||
| type: 0, | ||
| value, | ||
@@ -1623,11 +1623,11 @@ location | ||
| this.bumpSpace(); | ||
| if (this.isEOF()) return this.error(ErrorKind.EXPECT_ARGUMENT_CLOSING_BRACE, createLocation(openingBracePosition, this.clonePosition())); | ||
| if (this.isEOF()) return this.error(1, createLocation(openingBracePosition, this.clonePosition())); | ||
| if (this.char() === 125) { | ||
| this.bump(); | ||
| return this.error(ErrorKind.EMPTY_ARGUMENT, createLocation(openingBracePosition, this.clonePosition())); | ||
| return this.error(2, createLocation(openingBracePosition, this.clonePosition())); | ||
| } | ||
| let value = this.parseIdentifierIfPossible().value; | ||
| if (!value) return this.error(ErrorKind.MALFORMED_ARGUMENT, createLocation(openingBracePosition, this.clonePosition())); | ||
| if (!value) return this.error(3, createLocation(openingBracePosition, this.clonePosition())); | ||
| this.bumpSpace(); | ||
| if (this.isEOF()) return this.error(ErrorKind.EXPECT_ARGUMENT_CLOSING_BRACE, createLocation(openingBracePosition, this.clonePosition())); | ||
| if (this.isEOF()) return this.error(1, createLocation(openingBracePosition, this.clonePosition())); | ||
| switch (this.char()) { | ||
@@ -1638,3 +1638,3 @@ case 125: | ||
| val: { | ||
| type: TYPE.argument, | ||
| type: 1, | ||
| value, | ||
@@ -1648,5 +1648,5 @@ location: createLocation(openingBracePosition, this.clonePosition()) | ||
| this.bumpSpace(); | ||
| if (this.isEOF()) return this.error(ErrorKind.EXPECT_ARGUMENT_CLOSING_BRACE, createLocation(openingBracePosition, this.clonePosition())); | ||
| if (this.isEOF()) return this.error(1, createLocation(openingBracePosition, this.clonePosition())); | ||
| return this.parseArgumentOptions(nestingLevel, expectingCloseTag, value, openingBracePosition); | ||
| default: return this.error(ErrorKind.MALFORMED_ARGUMENT, createLocation(openingBracePosition, this.clonePosition())); | ||
| default: return this.error(3, createLocation(openingBracePosition, this.clonePosition())); | ||
| } | ||
@@ -1674,3 +1674,3 @@ } | ||
| switch (argType) { | ||
| case "": return this.error(ErrorKind.EXPECT_ARGUMENT_TYPE, createLocation(typeStartPosition, typeEndPosition)); | ||
| case "": return this.error(4, createLocation(typeStartPosition, typeEndPosition)); | ||
| case "number": | ||
@@ -1687,3 +1687,3 @@ case "date": | ||
| const style = trimEnd(result.val); | ||
| if (style.length === 0) return this.error(ErrorKind.EXPECT_ARGUMENT_STYLE, createLocation(this.clonePosition(), this.clonePosition())); | ||
| if (style.length === 0) return this.error(6, createLocation(this.clonePosition(), this.clonePosition())); | ||
| styleAndLocation = { | ||
@@ -1704,3 +1704,3 @@ style, | ||
| val: { | ||
| type: TYPE.number, | ||
| type: 2, | ||
| value, | ||
@@ -1713,7 +1713,7 @@ location, | ||
| } else { | ||
| if (skeleton.length === 0) return this.error(ErrorKind.EXPECT_DATE_TIME_SKELETON, location); | ||
| if (skeleton.length === 0) return this.error(10, location); | ||
| let dateTimePattern = skeleton; | ||
| if (this.locale) dateTimePattern = getBestPattern(skeleton, this.locale); | ||
| const style = { | ||
| type: SKELETON_TYPE.dateTime, | ||
| type: 1, | ||
| pattern: dateTimePattern, | ||
@@ -1725,3 +1725,3 @@ location: styleAndLocation.styleLocation, | ||
| val: { | ||
| type: argType === "date" ? TYPE.date : TYPE.time, | ||
| type: argType === "date" ? 3 : 4, | ||
| value, | ||
@@ -1737,3 +1737,3 @@ location, | ||
| val: { | ||
| type: argType === "number" ? TYPE.number : argType === "date" ? TYPE.date : TYPE.time, | ||
| type: argType === "number" ? 2 : argType === "date" ? 3 : 4, | ||
| value, | ||
@@ -1751,3 +1751,3 @@ location, | ||
| this.bumpSpace(); | ||
| if (!this.bumpIf(",")) return this.error(ErrorKind.EXPECT_SELECT_ARGUMENT_OPTIONS, createLocation(typeEndPosition, { ...typeEndPosition })); | ||
| if (!this.bumpIf(",")) return this.error(12, createLocation(typeEndPosition, { ...typeEndPosition })); | ||
| this.bumpSpace(); | ||
@@ -1757,5 +1757,5 @@ let identifierAndLocation = this.parseIdentifierIfPossible(); | ||
| if (argType !== "select" && identifierAndLocation.value === "offset") { | ||
| if (!this.bumpIf(":")) return this.error(ErrorKind.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE, createLocation(this.clonePosition(), this.clonePosition())); | ||
| if (!this.bumpIf(":")) return this.error(13, createLocation(this.clonePosition(), this.clonePosition())); | ||
| this.bumpSpace(); | ||
| const result = this.tryParseDecimalInteger(ErrorKind.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE, ErrorKind.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE); | ||
| const result = this.tryParseDecimalInteger(13, 14); | ||
| if (result.err) return result; | ||
@@ -1773,3 +1773,3 @@ this.bumpSpace(); | ||
| val: { | ||
| type: TYPE.select, | ||
| type: 5, | ||
| value, | ||
@@ -1783,3 +1783,3 @@ options: fromEntries(optionsResult.val), | ||
| val: { | ||
| type: TYPE.plural, | ||
| type: 6, | ||
| value, | ||
@@ -1794,7 +1794,7 @@ options: fromEntries(optionsResult.val), | ||
| } | ||
| default: return this.error(ErrorKind.INVALID_ARGUMENT_TYPE, createLocation(typeStartPosition, typeEndPosition)); | ||
| default: return this.error(5, createLocation(typeStartPosition, typeEndPosition)); | ||
| } | ||
| } | ||
| tryParseArgumentClose(openingBracePosition) { | ||
| if (this.isEOF() || this.char() !== 125) return this.error(ErrorKind.EXPECT_ARGUMENT_CLOSING_BRACE, createLocation(openingBracePosition, this.clonePosition())); | ||
| if (this.isEOF() || this.char() !== 125) return this.error(1, createLocation(openingBracePosition, this.clonePosition())); | ||
| this.bump(); | ||
@@ -1816,3 +1816,3 @@ return { | ||
| let apostrophePosition = this.clonePosition(); | ||
| if (!this.bumpUntil("'")) return this.error(ErrorKind.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE, createLocation(apostrophePosition, this.clonePosition())); | ||
| if (!this.bumpUntil("'")) return this.error(11, createLocation(apostrophePosition, this.clonePosition())); | ||
| this.bump(); | ||
@@ -1846,7 +1846,7 @@ break; | ||
| } catch { | ||
| return this.error(ErrorKind.INVALID_NUMBER_SKELETON, location); | ||
| return this.error(7, location); | ||
| } | ||
| return { | ||
| val: { | ||
| type: SKELETON_TYPE.number, | ||
| type: 0, | ||
| tokens, | ||
@@ -1878,3 +1878,3 @@ location, | ||
| if (parentArgType !== "select" && this.bumpIf("=")) { | ||
| const result = this.tryParseDecimalInteger(ErrorKind.EXPECT_PLURAL_ARGUMENT_SELECTOR, ErrorKind.INVALID_PLURAL_ARGUMENT_SELECTOR); | ||
| const result = this.tryParseDecimalInteger(16, 19); | ||
| if (result.err) return result; | ||
@@ -1885,7 +1885,7 @@ selectorLocation = createLocation(startPosition, this.clonePosition()); | ||
| } | ||
| if (parsedSelectors.has(selector)) return this.error(parentArgType === "select" ? ErrorKind.DUPLICATE_SELECT_ARGUMENT_SELECTOR : ErrorKind.DUPLICATE_PLURAL_ARGUMENT_SELECTOR, selectorLocation); | ||
| if (parsedSelectors.has(selector)) return this.error(parentArgType === "select" ? 21 : 20, selectorLocation); | ||
| if (selector === "other") hasOtherClause = true; | ||
| this.bumpSpace(); | ||
| const openingBracePosition = this.clonePosition(); | ||
| if (!this.bumpIf("{")) return this.error(parentArgType === "select" ? ErrorKind.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT : ErrorKind.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT, createLocation(this.clonePosition(), this.clonePosition())); | ||
| if (!this.bumpIf("{")) return this.error(parentArgType === "select" ? 17 : 18, createLocation(this.clonePosition(), this.clonePosition())); | ||
| const fragmentResult = this.parseMessage(nestingLevel + 1, parentArgType, expectCloseTag); | ||
@@ -1903,4 +1903,4 @@ if (fragmentResult.err) return fragmentResult; | ||
| } | ||
| if (options.length === 0) return this.error(parentArgType === "select" ? ErrorKind.EXPECT_SELECT_ARGUMENT_SELECTOR : ErrorKind.EXPECT_PLURAL_ARGUMENT_SELECTOR, createLocation(this.clonePosition(), this.clonePosition())); | ||
| if (this.requiresOtherClause && !hasOtherClause) return this.error(ErrorKind.MISSING_OTHER_CLAUSE, createLocation(this.clonePosition(), this.clonePosition())); | ||
| if (options.length === 0) return this.error(parentArgType === "select" ? 15 : 16, createLocation(this.clonePosition(), this.clonePosition())); | ||
| if (this.requiresOtherClause && !hasOtherClause) return this.error(22, createLocation(this.clonePosition(), this.clonePosition())); | ||
| return { | ||
@@ -2072,3 +2072,3 @@ val: options, | ||
| const existingType = vars.get(el.value); | ||
| if (existingType !== el.type && existingType !== TYPE.plural && existingType !== TYPE.select) throw new Error(`Variable ${el.value} has conflicting types`); | ||
| if (existingType !== el.type && existingType !== 6 && existingType !== 5) throw new Error(`Variable ${el.value} has conflicting types`); | ||
| } else vars.set(el.value, el.type); | ||
@@ -2075,0 +2075,0 @@ if (isPluralElement(el) || isSelectElement(el)) { |
+10
-10
@@ -43,24 +43,24 @@ //#region packages/icu-messageformat-parser/types.ts | ||
| function isArgumentElement(el) { | ||
| return el.type === TYPE.argument; | ||
| return el.type === 1; | ||
| } | ||
| function isNumberElement(el) { | ||
| return el.type === TYPE.number; | ||
| return el.type === 2; | ||
| } | ||
| function isDateElement(el) { | ||
| return el.type === TYPE.date; | ||
| return el.type === 3; | ||
| } | ||
| function isTimeElement(el) { | ||
| return el.type === TYPE.time; | ||
| return el.type === 4; | ||
| } | ||
| function isSelectElement(el) { | ||
| return el.type === TYPE.select; | ||
| return el.type === 5; | ||
| } | ||
| function isPluralElement(el) { | ||
| return el.type === TYPE.plural; | ||
| return el.type === 6; | ||
| } | ||
| function isPoundElement(el) { | ||
| return el.type === TYPE.pound; | ||
| return el.type === 7; | ||
| } | ||
| function isTagElement(el) { | ||
| return el.type === TYPE.tag; | ||
| return el.type === 8; | ||
| } | ||
@@ -86,3 +86,3 @@ //#endregion | ||
| if (isPoundElement(el)) return { | ||
| type: TYPE.number, | ||
| type: 2, | ||
| value: variableName, | ||
@@ -162,3 +162,3 @@ style: null, | ||
| const existingType = vars.get(el.value); | ||
| if (existingType !== el.type && existingType !== TYPE.plural && existingType !== TYPE.select) throw new Error(`Variable ${el.value} has conflicting types`); | ||
| if (existingType !== el.type && existingType !== 6 && existingType !== 5) throw new Error(`Variable ${el.value} has conflicting types`); | ||
| } else vars.set(el.value, el.type); | ||
@@ -165,0 +165,0 @@ if (isPluralElement(el) || isSelectElement(el)) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"manipulator.js","names":[],"sources":["../types.ts","../manipulator.ts"],"sourcesContent":["import type {NumberFormatOptions} from '#packages/ecma402-abstract/types/number.js'\nimport {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\n\nexport interface ExtendedNumberFormatOptions extends NumberFormatOptions {\n scale?: number\n}\n\nexport enum TYPE {\n /**\n * Raw text\n */\n literal,\n /**\n * Variable w/o any format, e.g `var` in `this is a {var}`\n */\n argument,\n /**\n * Variable w/ number format\n */\n number,\n /**\n * Variable w/ date format\n */\n date,\n /**\n * Variable w/ time format\n */\n time,\n /**\n * Variable w/ select format\n */\n select,\n /**\n * Variable w/ plural format\n */\n plural,\n /**\n * Only possible within plural argument.\n * This is the `#` symbol that will be substituted with the count.\n */\n pound,\n /**\n * XML-like tag\n */\n tag,\n}\n\nexport enum SKELETON_TYPE {\n number,\n dateTime,\n}\n\nexport interface LocationDetails {\n offset: number\n line: number\n column: number\n}\nexport interface Location {\n start: LocationDetails\n end: LocationDetails\n}\n\nexport interface BaseElement<T extends TYPE> {\n type: T\n value: string\n location?: Location\n}\n\nexport type LiteralElement = BaseElement<TYPE.literal>\nexport type ArgumentElement = BaseElement<TYPE.argument>\nexport interface TagElement extends BaseElement<TYPE.tag> {\n children: MessageFormatElement[]\n}\n\nexport interface SimpleFormatElement<\n T extends TYPE,\n S extends Skeleton,\n> extends BaseElement<T> {\n style?: string | S | null\n}\n\nexport type NumberElement = SimpleFormatElement<TYPE.number, NumberSkeleton>\nexport type DateElement = SimpleFormatElement<TYPE.date, DateTimeSkeleton>\nexport type TimeElement = SimpleFormatElement<TYPE.time, DateTimeSkeleton>\n\nexport type ValidPluralRule =\n | 'zero'\n | 'one'\n | 'two'\n | 'few'\n | 'many'\n | 'other'\n | string\n\nexport interface PluralOrSelectOption {\n value: MessageFormatElement[]\n location?: Location\n}\n\nexport interface SelectElement extends BaseElement<TYPE.select> {\n options: Record<string, PluralOrSelectOption>\n}\n\nexport interface PluralElement extends BaseElement<TYPE.plural> {\n options: Record<ValidPluralRule, PluralOrSelectOption>\n offset: number\n pluralType: Intl.PluralRulesOptions['type']\n}\n\nexport interface PoundElement {\n type: TYPE.pound\n location?: Location\n}\n\nexport type MessageFormatElement =\n | ArgumentElement\n | DateElement\n | LiteralElement\n | NumberElement\n | PluralElement\n | PoundElement\n | SelectElement\n | TagElement\n | TimeElement\n\nexport interface NumberSkeleton {\n type: SKELETON_TYPE.number\n tokens: NumberSkeletonToken[]\n location?: Location\n parsedOptions: ExtendedNumberFormatOptions\n}\n\nexport interface DateTimeSkeleton {\n type: SKELETON_TYPE.dateTime\n pattern: string\n location?: Location\n parsedOptions: Intl.DateTimeFormatOptions\n}\n\nexport type Skeleton = NumberSkeleton | DateTimeSkeleton\n\n/**\n * Type Guards\n */\nexport function isLiteralElement(\n el: MessageFormatElement\n): el is LiteralElement {\n return el.type === TYPE.literal\n}\nexport function isArgumentElement(\n el: MessageFormatElement\n): el is ArgumentElement {\n return el.type === TYPE.argument\n}\nexport function isNumberElement(el: MessageFormatElement): el is NumberElement {\n return el.type === TYPE.number\n}\nexport function isDateElement(el: MessageFormatElement): el is DateElement {\n return el.type === TYPE.date\n}\nexport function isTimeElement(el: MessageFormatElement): el is TimeElement {\n return el.type === TYPE.time\n}\nexport function isSelectElement(el: MessageFormatElement): el is SelectElement {\n return el.type === TYPE.select\n}\nexport function isPluralElement(el: MessageFormatElement): el is PluralElement {\n return el.type === TYPE.plural\n}\nexport function isPoundElement(el: MessageFormatElement): el is PoundElement {\n return el.type === TYPE.pound\n}\nexport function isTagElement(el: MessageFormatElement): el is TagElement {\n return el.type === TYPE.tag\n}\nexport function isNumberSkeleton(\n el: NumberElement['style'] | Skeleton\n): el is NumberSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number)\n}\nexport function isDateTimeSkeleton(\n el?: DateElement['style'] | TimeElement['style'] | Skeleton\n): el is DateTimeSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime)\n}\n\nexport function createLiteralElement(value: string): LiteralElement {\n return {\n type: TYPE.literal,\n value,\n }\n}\n\nexport function createNumberElement(\n value: string,\n style?: string | null\n): NumberElement {\n return {\n type: TYPE.number,\n value,\n style,\n }\n}\n","import {\n isArgumentElement,\n isDateElement,\n isNumberElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n isTimeElement,\n type MessageFormatElement,\n type PluralElement,\n type PluralOrSelectOption,\n type SelectElement,\n TYPE,\n} from '#packages/icu-messageformat-parser/types.js'\n\nfunction cloneDeep<T>(obj: T): T {\n if (Array.isArray(obj)) {\n // @ts-expect-error meh\n return obj.map(cloneDeep)\n }\n if (obj !== null && typeof obj === 'object') {\n // @ts-expect-error meh\n return Object.keys(obj).reduce((cloned, k) => {\n // @ts-expect-error meh\n cloned[k] = cloneDeep(obj[k])\n return cloned\n }, {})\n }\n return obj\n}\n\n/**\n * Replace pound elements with number elements referencing the given variable.\n * This is needed when nesting plurals - the # in the outer plural should become\n * an explicit variable reference when nested inside another plural.\n * GH #4202\n */\nfunction replacePoundWithArgument(\n ast: MessageFormatElement[],\n variableName: string\n): MessageFormatElement[] {\n return ast.map(el => {\n if (isPoundElement(el)) {\n // Replace # with {variableName, number}\n return {\n type: TYPE.number,\n value: variableName,\n style: null,\n location: el.location,\n }\n }\n if (isPluralElement(el) || isSelectElement(el)) {\n // Recursively process options\n const newOptions: Record<string, PluralOrSelectOption> = {}\n for (const key of Object.keys(el.options)) {\n newOptions[key] = {\n value: replacePoundWithArgument(el.options[key].value, variableName),\n }\n }\n return {...el, options: newOptions}\n }\n if (isTagElement(el)) {\n return {\n ...el,\n children: replacePoundWithArgument(el.children, variableName),\n }\n }\n return el\n })\n}\n\nfunction hoistPluralOrSelectElement(\n ast: MessageFormatElement[],\n el: PluralElement | SelectElement,\n positionToInject: number\n) {\n // pull this out of the ast and move it to the top\n const cloned = cloneDeep(el)\n const {options} = cloned\n\n // GH #4202: Check if there are other plural/select elements after this one\n const afterElements = ast.slice(positionToInject + 1)\n const hasSubsequentPluralOrSelect = afterElements.some(\n isPluralOrSelectElement\n )\n\n cloned.options = Object.keys(options).reduce(\n (all: Record<string, PluralOrSelectOption>, k) => {\n let optionValue = options[k].value\n\n // GH #4202: If there are subsequent plurals/selects and this is a plural,\n // replace # with explicit variable reference to avoid ambiguity\n if (hasSubsequentPluralOrSelect && isPluralElement(el)) {\n optionValue = replacePoundWithArgument(optionValue, el.value)\n }\n\n const newValue = hoistSelectors([\n ...ast.slice(0, positionToInject),\n ...optionValue,\n ...afterElements,\n ])\n all[k] = {\n value: newValue,\n }\n return all\n },\n {}\n )\n return cloned\n}\n\nfunction isPluralOrSelectElement(\n el: MessageFormatElement\n): el is PluralElement | SelectElement {\n return isPluralElement(el) || isSelectElement(el)\n}\n\nfunction findPluralOrSelectElement(ast: MessageFormatElement[]): boolean {\n return !!ast.find(el => {\n if (isPluralOrSelectElement(el)) {\n return true\n }\n if (isTagElement(el)) {\n return findPluralOrSelectElement(el.children)\n }\n return false\n })\n}\n\n/**\n * Hoist all selectors to the beginning of the AST & flatten the\n * resulting options. E.g:\n * \"I have {count, plural, one{a dog} other{many dogs}}\"\n * becomes \"{count, plural, one{I have a dog} other{I have many dogs}}\".\n * If there are multiple selectors, the order of which one is hoisted 1st\n * is non-deterministic.\n * The goal is to provide as many full sentences as possible since fragmented\n * sentences are not translator-friendly\n * @param ast AST\n */\nexport function hoistSelectors(\n ast: MessageFormatElement[]\n): MessageFormatElement[] {\n for (let i = 0; i < ast.length; i++) {\n const el = ast[i]\n if (isPluralOrSelectElement(el)) {\n return [hoistPluralOrSelectElement(ast, el, i)]\n }\n if (isTagElement(el) && findPluralOrSelectElement([el])) {\n throw new Error(\n 'Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option'\n )\n }\n }\n return ast\n}\n\n/**\n * Collect all variables in an AST to Record<string, TYPE>\n * @param ast AST to collect variables from\n * @param vars Record of variable name to variable type\n */\nfunction collectVariables(\n ast: MessageFormatElement[],\n vars: Map<string, TYPE> = new Map<string, TYPE>()\n): void {\n ast.forEach(el => {\n if (\n isArgumentElement(el) ||\n isDateElement(el) ||\n isTimeElement(el) ||\n isNumberElement(el)\n ) {\n // If the variable was already registered as a plural/select, it's normal\n // for it to also appear inside as number/date/time/argument — not a conflict.\n if (vars.has(el.value)) {\n const existingType = vars.get(el.value)!\n if (\n existingType !== el.type &&\n existingType !== TYPE.plural &&\n existingType !== TYPE.select\n ) {\n throw new Error(`Variable ${el.value} has conflicting types`)\n }\n } else {\n vars.set(el.value, el.type)\n }\n }\n\n if (isPluralElement(el) || isSelectElement(el)) {\n vars.set(el.value, el.type)\n Object.keys(el.options).forEach(k => {\n collectVariables(el.options[k].value, vars)\n })\n }\n\n if (isTagElement(el)) {\n vars.set(el.value, el.type)\n collectVariables(el.children, vars)\n }\n })\n}\n\ninterface IsStructurallySameResult {\n error?: Error\n success: boolean\n}\n\n/**\n * Check if 2 ASTs are structurally the same. This primarily means that\n * they have the same variables with the same type\n * @param a\n * @param b\n * @returns\n */\nexport function isStructurallySame(\n a: MessageFormatElement[],\n b: MessageFormatElement[]\n): IsStructurallySameResult {\n const aVars = new Map<string, TYPE>()\n const bVars = new Map<string, TYPE>()\n collectVariables(a, aVars)\n collectVariables(b, bVars)\n\n if (aVars.size !== bVars.size) {\n return {\n success: false,\n error: new Error(\n `Different number of variables: [${Array.from(aVars.keys()).join(', ')}] vs [${Array.from(bVars.keys()).join(', ')}]`\n ),\n }\n }\n\n return Array.from(aVars.entries()).reduce<IsStructurallySameResult>(\n (result, [key, type]) => {\n if (!result.success) {\n return result\n }\n const bType = bVars.get(key)\n if (bType == null) {\n return {\n success: false,\n error: new Error(`Missing variable ${key} in message`),\n }\n }\n if (bType !== type) {\n return {\n success: false,\n error: new Error(\n `Variable ${key} has conflicting types: ${TYPE[type]} vs ${TYPE[bType]}`\n ),\n }\n }\n return result\n },\n {success: true}\n )\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"manipulator.js","names":[],"sources":["../types.ts","../manipulator.ts"],"sourcesContent":["import type {NumberFormatOptions} from '#packages/ecma402-abstract/types/number.js'\nimport {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\n\nexport interface ExtendedNumberFormatOptions extends NumberFormatOptions {\n scale?: number\n}\n\nexport enum TYPE {\n /**\n * Raw text\n */\n literal,\n /**\n * Variable w/o any format, e.g `var` in `this is a {var}`\n */\n argument,\n /**\n * Variable w/ number format\n */\n number,\n /**\n * Variable w/ date format\n */\n date,\n /**\n * Variable w/ time format\n */\n time,\n /**\n * Variable w/ select format\n */\n select,\n /**\n * Variable w/ plural format\n */\n plural,\n /**\n * Only possible within plural argument.\n * This is the `#` symbol that will be substituted with the count.\n */\n pound,\n /**\n * XML-like tag\n */\n tag,\n}\n\nexport enum SKELETON_TYPE {\n number,\n dateTime,\n}\n\nexport interface LocationDetails {\n offset: number\n line: number\n column: number\n}\nexport interface Location {\n start: LocationDetails\n end: LocationDetails\n}\n\nexport interface BaseElement<T extends TYPE> {\n type: T\n value: string\n location?: Location\n}\n\nexport type LiteralElement = BaseElement<TYPE.literal>\nexport type ArgumentElement = BaseElement<TYPE.argument>\nexport interface TagElement extends BaseElement<TYPE.tag> {\n children: MessageFormatElement[]\n}\n\nexport interface SimpleFormatElement<\n T extends TYPE,\n S extends Skeleton,\n> extends BaseElement<T> {\n style?: string | S | null\n}\n\nexport type NumberElement = SimpleFormatElement<TYPE.number, NumberSkeleton>\nexport type DateElement = SimpleFormatElement<TYPE.date, DateTimeSkeleton>\nexport type TimeElement = SimpleFormatElement<TYPE.time, DateTimeSkeleton>\n\nexport type ValidPluralRule =\n | 'zero'\n | 'one'\n | 'two'\n | 'few'\n | 'many'\n | 'other'\n | string\n\nexport interface PluralOrSelectOption {\n value: MessageFormatElement[]\n location?: Location\n}\n\nexport interface SelectElement extends BaseElement<TYPE.select> {\n options: Record<string, PluralOrSelectOption>\n}\n\nexport interface PluralElement extends BaseElement<TYPE.plural> {\n options: Record<ValidPluralRule, PluralOrSelectOption>\n offset: number\n pluralType: Intl.PluralRulesOptions['type']\n}\n\nexport interface PoundElement {\n type: TYPE.pound\n location?: Location\n}\n\nexport type MessageFormatElement =\n | ArgumentElement\n | DateElement\n | LiteralElement\n | NumberElement\n | PluralElement\n | PoundElement\n | SelectElement\n | TagElement\n | TimeElement\n\nexport interface NumberSkeleton {\n type: SKELETON_TYPE.number\n tokens: NumberSkeletonToken[]\n location?: Location\n parsedOptions: ExtendedNumberFormatOptions\n}\n\nexport interface DateTimeSkeleton {\n type: SKELETON_TYPE.dateTime\n pattern: string\n location?: Location\n parsedOptions: Intl.DateTimeFormatOptions\n}\n\nexport type Skeleton = NumberSkeleton | DateTimeSkeleton\n\n/**\n * Type Guards\n */\nexport function isLiteralElement(\n el: MessageFormatElement\n): el is LiteralElement {\n return el.type === TYPE.literal\n}\nexport function isArgumentElement(\n el: MessageFormatElement\n): el is ArgumentElement {\n return el.type === TYPE.argument\n}\nexport function isNumberElement(el: MessageFormatElement): el is NumberElement {\n return el.type === TYPE.number\n}\nexport function isDateElement(el: MessageFormatElement): el is DateElement {\n return el.type === TYPE.date\n}\nexport function isTimeElement(el: MessageFormatElement): el is TimeElement {\n return el.type === TYPE.time\n}\nexport function isSelectElement(el: MessageFormatElement): el is SelectElement {\n return el.type === TYPE.select\n}\nexport function isPluralElement(el: MessageFormatElement): el is PluralElement {\n return el.type === TYPE.plural\n}\nexport function isPoundElement(el: MessageFormatElement): el is PoundElement {\n return el.type === TYPE.pound\n}\nexport function isTagElement(el: MessageFormatElement): el is TagElement {\n return el.type === TYPE.tag\n}\nexport function isNumberSkeleton(\n el: NumberElement['style'] | Skeleton\n): el is NumberSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number)\n}\nexport function isDateTimeSkeleton(\n el?: DateElement['style'] | TimeElement['style'] | Skeleton\n): el is DateTimeSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime)\n}\n\nexport function createLiteralElement(value: string): LiteralElement {\n return {\n type: TYPE.literal,\n value,\n }\n}\n\nexport function createNumberElement(\n value: string,\n style?: string | null\n): NumberElement {\n return {\n type: TYPE.number,\n value,\n style,\n }\n}\n","import {\n isArgumentElement,\n isDateElement,\n isNumberElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n isTimeElement,\n type MessageFormatElement,\n type PluralElement,\n type PluralOrSelectOption,\n type SelectElement,\n TYPE,\n} from '#packages/icu-messageformat-parser/types.js'\n\nfunction cloneDeep<T>(obj: T): T {\n if (Array.isArray(obj)) {\n // @ts-expect-error meh\n return obj.map(cloneDeep)\n }\n if (obj !== null && typeof obj === 'object') {\n // @ts-expect-error meh\n return Object.keys(obj).reduce((cloned, k) => {\n // @ts-expect-error meh\n cloned[k] = cloneDeep(obj[k])\n return cloned\n }, {})\n }\n return obj\n}\n\n/**\n * Replace pound elements with number elements referencing the given variable.\n * This is needed when nesting plurals - the # in the outer plural should become\n * an explicit variable reference when nested inside another plural.\n * GH #4202\n */\nfunction replacePoundWithArgument(\n ast: MessageFormatElement[],\n variableName: string\n): MessageFormatElement[] {\n return ast.map(el => {\n if (isPoundElement(el)) {\n // Replace # with {variableName, number}\n return {\n type: TYPE.number,\n value: variableName,\n style: null,\n location: el.location,\n }\n }\n if (isPluralElement(el) || isSelectElement(el)) {\n // Recursively process options\n const newOptions: Record<string, PluralOrSelectOption> = {}\n for (const key of Object.keys(el.options)) {\n newOptions[key] = {\n value: replacePoundWithArgument(el.options[key].value, variableName),\n }\n }\n return {...el, options: newOptions}\n }\n if (isTagElement(el)) {\n return {\n ...el,\n children: replacePoundWithArgument(el.children, variableName),\n }\n }\n return el\n })\n}\n\nfunction hoistPluralOrSelectElement(\n ast: MessageFormatElement[],\n el: PluralElement | SelectElement,\n positionToInject: number\n) {\n // pull this out of the ast and move it to the top\n const cloned = cloneDeep(el)\n const {options} = cloned\n\n // GH #4202: Check if there are other plural/select elements after this one\n const afterElements = ast.slice(positionToInject + 1)\n const hasSubsequentPluralOrSelect = afterElements.some(\n isPluralOrSelectElement\n )\n\n cloned.options = Object.keys(options).reduce(\n (all: Record<string, PluralOrSelectOption>, k) => {\n let optionValue = options[k].value\n\n // GH #4202: If there are subsequent plurals/selects and this is a plural,\n // replace # with explicit variable reference to avoid ambiguity\n if (hasSubsequentPluralOrSelect && isPluralElement(el)) {\n optionValue = replacePoundWithArgument(optionValue, el.value)\n }\n\n const newValue = hoistSelectors([\n ...ast.slice(0, positionToInject),\n ...optionValue,\n ...afterElements,\n ])\n all[k] = {\n value: newValue,\n }\n return all\n },\n {}\n )\n return cloned\n}\n\nfunction isPluralOrSelectElement(\n el: MessageFormatElement\n): el is PluralElement | SelectElement {\n return isPluralElement(el) || isSelectElement(el)\n}\n\nfunction findPluralOrSelectElement(ast: MessageFormatElement[]): boolean {\n return !!ast.find(el => {\n if (isPluralOrSelectElement(el)) {\n return true\n }\n if (isTagElement(el)) {\n return findPluralOrSelectElement(el.children)\n }\n return false\n })\n}\n\n/**\n * Hoist all selectors to the beginning of the AST & flatten the\n * resulting options. E.g:\n * \"I have {count, plural, one{a dog} other{many dogs}}\"\n * becomes \"{count, plural, one{I have a dog} other{I have many dogs}}\".\n * If there are multiple selectors, the order of which one is hoisted 1st\n * is non-deterministic.\n * The goal is to provide as many full sentences as possible since fragmented\n * sentences are not translator-friendly\n * @param ast AST\n */\nexport function hoistSelectors(\n ast: MessageFormatElement[]\n): MessageFormatElement[] {\n for (let i = 0; i < ast.length; i++) {\n const el = ast[i]\n if (isPluralOrSelectElement(el)) {\n return [hoistPluralOrSelectElement(ast, el, i)]\n }\n if (isTagElement(el) && findPluralOrSelectElement([el])) {\n throw new Error(\n 'Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option'\n )\n }\n }\n return ast\n}\n\n/**\n * Collect all variables in an AST to Record<string, TYPE>\n * @param ast AST to collect variables from\n * @param vars Record of variable name to variable type\n */\nfunction collectVariables(\n ast: MessageFormatElement[],\n vars: Map<string, TYPE> = new Map<string, TYPE>()\n): void {\n ast.forEach(el => {\n if (\n isArgumentElement(el) ||\n isDateElement(el) ||\n isTimeElement(el) ||\n isNumberElement(el)\n ) {\n // If the variable was already registered as a plural/select, it's normal\n // for it to also appear inside as number/date/time/argument — not a conflict.\n if (vars.has(el.value)) {\n const existingType = vars.get(el.value)!\n if (\n existingType !== el.type &&\n existingType !== TYPE.plural &&\n existingType !== TYPE.select\n ) {\n throw new Error(`Variable ${el.value} has conflicting types`)\n }\n } else {\n vars.set(el.value, el.type)\n }\n }\n\n if (isPluralElement(el) || isSelectElement(el)) {\n vars.set(el.value, el.type)\n Object.keys(el.options).forEach(k => {\n collectVariables(el.options[k].value, vars)\n })\n }\n\n if (isTagElement(el)) {\n vars.set(el.value, el.type)\n collectVariables(el.children, vars)\n }\n })\n}\n\ninterface IsStructurallySameResult {\n error?: Error\n success: boolean\n}\n\n/**\n * Check if 2 ASTs are structurally the same. This primarily means that\n * they have the same variables with the same type\n * @param a\n * @param b\n * @returns\n */\nexport function isStructurallySame(\n a: MessageFormatElement[],\n b: MessageFormatElement[]\n): IsStructurallySameResult {\n const aVars = new Map<string, TYPE>()\n const bVars = new Map<string, TYPE>()\n collectVariables(a, aVars)\n collectVariables(b, bVars)\n\n if (aVars.size !== bVars.size) {\n return {\n success: false,\n error: new Error(\n `Different number of variables: [${Array.from(aVars.keys()).join(', ')}] vs [${Array.from(bVars.keys()).join(', ')}]`\n ),\n }\n }\n\n return Array.from(aVars.entries()).reduce<IsStructurallySameResult>(\n (result, [key, type]) => {\n if (!result.success) {\n return result\n }\n const bType = bVars.get(key)\n if (bType == null) {\n return {\n success: false,\n error: new Error(`Missing variable ${key} in message`),\n }\n }\n if (bType !== type) {\n return {\n success: false,\n error: new Error(\n `Variable ${key} has conflicting types: ${TYPE[type]} vs ${TYPE[bType]}`\n ),\n }\n }\n return result\n },\n {success: true}\n )\n}\n"],"mappings":"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"} |
+14
-14
@@ -51,37 +51,37 @@ //#region packages/icu-messageformat-parser/types.ts | ||
| function isLiteralElement(el) { | ||
| return el.type === TYPE.literal; | ||
| return el.type === 0; | ||
| } | ||
| function isArgumentElement(el) { | ||
| return el.type === TYPE.argument; | ||
| return el.type === 1; | ||
| } | ||
| function isNumberElement(el) { | ||
| return el.type === TYPE.number; | ||
| return el.type === 2; | ||
| } | ||
| function isDateElement(el) { | ||
| return el.type === TYPE.date; | ||
| return el.type === 3; | ||
| } | ||
| function isTimeElement(el) { | ||
| return el.type === TYPE.time; | ||
| return el.type === 4; | ||
| } | ||
| function isSelectElement(el) { | ||
| return el.type === TYPE.select; | ||
| return el.type === 5; | ||
| } | ||
| function isPluralElement(el) { | ||
| return el.type === TYPE.plural; | ||
| return el.type === 6; | ||
| } | ||
| function isPoundElement(el) { | ||
| return el.type === TYPE.pound; | ||
| return el.type === 7; | ||
| } | ||
| function isTagElement(el) { | ||
| return el.type === TYPE.tag; | ||
| return el.type === 8; | ||
| } | ||
| function isNumberSkeleton(el) { | ||
| return !!(el && typeof el === "object" && el.type === SKELETON_TYPE.number); | ||
| return !!(el && typeof el === "object" && el.type === 0); | ||
| } | ||
| function isDateTimeSkeleton(el) { | ||
| return !!(el && typeof el === "object" && el.type === SKELETON_TYPE.dateTime); | ||
| return !!(el && typeof el === "object" && el.type === 1); | ||
| } | ||
| function createLiteralElement(value) { | ||
| return { | ||
| type: TYPE.literal, | ||
| type: 0, | ||
| value | ||
@@ -92,3 +92,3 @@ }; | ||
| return { | ||
| type: TYPE.number, | ||
| type: 2, | ||
| value, | ||
@@ -109,3 +109,3 @@ style | ||
| const existingType = vars.get(el.value); | ||
| if (existingType !== el.type && existingType !== TYPE.plural && existingType !== TYPE.select) throw new Error(`Variable ${el.value} has conflicting types`); | ||
| if (existingType !== el.type && existingType !== 6 && existingType !== 5) throw new Error(`Variable ${el.value} has conflicting types`); | ||
| } else vars.set(el.value, el.type); | ||
@@ -112,0 +112,0 @@ if (isPluralElement(el) || isSelectElement(el)) { |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"no-parser.js","names":[],"sources":["../types.ts","../manipulator.ts","../no-parser.ts"],"sourcesContent":["import type {NumberFormatOptions} from '#packages/ecma402-abstract/types/number.js'\nimport {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\n\nexport interface ExtendedNumberFormatOptions extends NumberFormatOptions {\n scale?: number\n}\n\nexport enum TYPE {\n /**\n * Raw text\n */\n literal,\n /**\n * Variable w/o any format, e.g `var` in `this is a {var}`\n */\n argument,\n /**\n * Variable w/ number format\n */\n number,\n /**\n * Variable w/ date format\n */\n date,\n /**\n * Variable w/ time format\n */\n time,\n /**\n * Variable w/ select format\n */\n select,\n /**\n * Variable w/ plural format\n */\n plural,\n /**\n * Only possible within plural argument.\n * This is the `#` symbol that will be substituted with the count.\n */\n pound,\n /**\n * XML-like tag\n */\n tag,\n}\n\nexport enum SKELETON_TYPE {\n number,\n dateTime,\n}\n\nexport interface LocationDetails {\n offset: number\n line: number\n column: number\n}\nexport interface Location {\n start: LocationDetails\n end: LocationDetails\n}\n\nexport interface BaseElement<T extends TYPE> {\n type: T\n value: string\n location?: Location\n}\n\nexport type LiteralElement = BaseElement<TYPE.literal>\nexport type ArgumentElement = BaseElement<TYPE.argument>\nexport interface TagElement extends BaseElement<TYPE.tag> {\n children: MessageFormatElement[]\n}\n\nexport interface SimpleFormatElement<\n T extends TYPE,\n S extends Skeleton,\n> extends BaseElement<T> {\n style?: string | S | null\n}\n\nexport type NumberElement = SimpleFormatElement<TYPE.number, NumberSkeleton>\nexport type DateElement = SimpleFormatElement<TYPE.date, DateTimeSkeleton>\nexport type TimeElement = SimpleFormatElement<TYPE.time, DateTimeSkeleton>\n\nexport type ValidPluralRule =\n | 'zero'\n | 'one'\n | 'two'\n | 'few'\n | 'many'\n | 'other'\n | string\n\nexport interface PluralOrSelectOption {\n value: MessageFormatElement[]\n location?: Location\n}\n\nexport interface SelectElement extends BaseElement<TYPE.select> {\n options: Record<string, PluralOrSelectOption>\n}\n\nexport interface PluralElement extends BaseElement<TYPE.plural> {\n options: Record<ValidPluralRule, PluralOrSelectOption>\n offset: number\n pluralType: Intl.PluralRulesOptions['type']\n}\n\nexport interface PoundElement {\n type: TYPE.pound\n location?: Location\n}\n\nexport type MessageFormatElement =\n | ArgumentElement\n | DateElement\n | LiteralElement\n | NumberElement\n | PluralElement\n | PoundElement\n | SelectElement\n | TagElement\n | TimeElement\n\nexport interface NumberSkeleton {\n type: SKELETON_TYPE.number\n tokens: NumberSkeletonToken[]\n location?: Location\n parsedOptions: ExtendedNumberFormatOptions\n}\n\nexport interface DateTimeSkeleton {\n type: SKELETON_TYPE.dateTime\n pattern: string\n location?: Location\n parsedOptions: Intl.DateTimeFormatOptions\n}\n\nexport type Skeleton = NumberSkeleton | DateTimeSkeleton\n\n/**\n * Type Guards\n */\nexport function isLiteralElement(\n el: MessageFormatElement\n): el is LiteralElement {\n return el.type === TYPE.literal\n}\nexport function isArgumentElement(\n el: MessageFormatElement\n): el is ArgumentElement {\n return el.type === TYPE.argument\n}\nexport function isNumberElement(el: MessageFormatElement): el is NumberElement {\n return el.type === TYPE.number\n}\nexport function isDateElement(el: MessageFormatElement): el is DateElement {\n return el.type === TYPE.date\n}\nexport function isTimeElement(el: MessageFormatElement): el is TimeElement {\n return el.type === TYPE.time\n}\nexport function isSelectElement(el: MessageFormatElement): el is SelectElement {\n return el.type === TYPE.select\n}\nexport function isPluralElement(el: MessageFormatElement): el is PluralElement {\n return el.type === TYPE.plural\n}\nexport function isPoundElement(el: MessageFormatElement): el is PoundElement {\n return el.type === TYPE.pound\n}\nexport function isTagElement(el: MessageFormatElement): el is TagElement {\n return el.type === TYPE.tag\n}\nexport function isNumberSkeleton(\n el: NumberElement['style'] | Skeleton\n): el is NumberSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number)\n}\nexport function isDateTimeSkeleton(\n el?: DateElement['style'] | TimeElement['style'] | Skeleton\n): el is DateTimeSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime)\n}\n\nexport function createLiteralElement(value: string): LiteralElement {\n return {\n type: TYPE.literal,\n value,\n }\n}\n\nexport function createNumberElement(\n value: string,\n style?: string | null\n): NumberElement {\n return {\n type: TYPE.number,\n value,\n style,\n }\n}\n","import {\n isArgumentElement,\n isDateElement,\n isNumberElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n isTimeElement,\n type MessageFormatElement,\n type PluralElement,\n type PluralOrSelectOption,\n type SelectElement,\n TYPE,\n} from '#packages/icu-messageformat-parser/types.js'\n\nfunction cloneDeep<T>(obj: T): T {\n if (Array.isArray(obj)) {\n // @ts-expect-error meh\n return obj.map(cloneDeep)\n }\n if (obj !== null && typeof obj === 'object') {\n // @ts-expect-error meh\n return Object.keys(obj).reduce((cloned, k) => {\n // @ts-expect-error meh\n cloned[k] = cloneDeep(obj[k])\n return cloned\n }, {})\n }\n return obj\n}\n\n/**\n * Replace pound elements with number elements referencing the given variable.\n * This is needed when nesting plurals - the # in the outer plural should become\n * an explicit variable reference when nested inside another plural.\n * GH #4202\n */\nfunction replacePoundWithArgument(\n ast: MessageFormatElement[],\n variableName: string\n): MessageFormatElement[] {\n return ast.map(el => {\n if (isPoundElement(el)) {\n // Replace # with {variableName, number}\n return {\n type: TYPE.number,\n value: variableName,\n style: null,\n location: el.location,\n }\n }\n if (isPluralElement(el) || isSelectElement(el)) {\n // Recursively process options\n const newOptions: Record<string, PluralOrSelectOption> = {}\n for (const key of Object.keys(el.options)) {\n newOptions[key] = {\n value: replacePoundWithArgument(el.options[key].value, variableName),\n }\n }\n return {...el, options: newOptions}\n }\n if (isTagElement(el)) {\n return {\n ...el,\n children: replacePoundWithArgument(el.children, variableName),\n }\n }\n return el\n })\n}\n\nfunction hoistPluralOrSelectElement(\n ast: MessageFormatElement[],\n el: PluralElement | SelectElement,\n positionToInject: number\n) {\n // pull this out of the ast and move it to the top\n const cloned = cloneDeep(el)\n const {options} = cloned\n\n // GH #4202: Check if there are other plural/select elements after this one\n const afterElements = ast.slice(positionToInject + 1)\n const hasSubsequentPluralOrSelect = afterElements.some(\n isPluralOrSelectElement\n )\n\n cloned.options = Object.keys(options).reduce(\n (all: Record<string, PluralOrSelectOption>, k) => {\n let optionValue = options[k].value\n\n // GH #4202: If there are subsequent plurals/selects and this is a plural,\n // replace # with explicit variable reference to avoid ambiguity\n if (hasSubsequentPluralOrSelect && isPluralElement(el)) {\n optionValue = replacePoundWithArgument(optionValue, el.value)\n }\n\n const newValue = hoistSelectors([\n ...ast.slice(0, positionToInject),\n ...optionValue,\n ...afterElements,\n ])\n all[k] = {\n value: newValue,\n }\n return all\n },\n {}\n )\n return cloned\n}\n\nfunction isPluralOrSelectElement(\n el: MessageFormatElement\n): el is PluralElement | SelectElement {\n return isPluralElement(el) || isSelectElement(el)\n}\n\nfunction findPluralOrSelectElement(ast: MessageFormatElement[]): boolean {\n return !!ast.find(el => {\n if (isPluralOrSelectElement(el)) {\n return true\n }\n if (isTagElement(el)) {\n return findPluralOrSelectElement(el.children)\n }\n return false\n })\n}\n\n/**\n * Hoist all selectors to the beginning of the AST & flatten the\n * resulting options. E.g:\n * \"I have {count, plural, one{a dog} other{many dogs}}\"\n * becomes \"{count, plural, one{I have a dog} other{I have many dogs}}\".\n * If there are multiple selectors, the order of which one is hoisted 1st\n * is non-deterministic.\n * The goal is to provide as many full sentences as possible since fragmented\n * sentences are not translator-friendly\n * @param ast AST\n */\nexport function hoistSelectors(\n ast: MessageFormatElement[]\n): MessageFormatElement[] {\n for (let i = 0; i < ast.length; i++) {\n const el = ast[i]\n if (isPluralOrSelectElement(el)) {\n return [hoistPluralOrSelectElement(ast, el, i)]\n }\n if (isTagElement(el) && findPluralOrSelectElement([el])) {\n throw new Error(\n 'Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option'\n )\n }\n }\n return ast\n}\n\n/**\n * Collect all variables in an AST to Record<string, TYPE>\n * @param ast AST to collect variables from\n * @param vars Record of variable name to variable type\n */\nfunction collectVariables(\n ast: MessageFormatElement[],\n vars: Map<string, TYPE> = new Map<string, TYPE>()\n): void {\n ast.forEach(el => {\n if (\n isArgumentElement(el) ||\n isDateElement(el) ||\n isTimeElement(el) ||\n isNumberElement(el)\n ) {\n // If the variable was already registered as a plural/select, it's normal\n // for it to also appear inside as number/date/time/argument — not a conflict.\n if (vars.has(el.value)) {\n const existingType = vars.get(el.value)!\n if (\n existingType !== el.type &&\n existingType !== TYPE.plural &&\n existingType !== TYPE.select\n ) {\n throw new Error(`Variable ${el.value} has conflicting types`)\n }\n } else {\n vars.set(el.value, el.type)\n }\n }\n\n if (isPluralElement(el) || isSelectElement(el)) {\n vars.set(el.value, el.type)\n Object.keys(el.options).forEach(k => {\n collectVariables(el.options[k].value, vars)\n })\n }\n\n if (isTagElement(el)) {\n vars.set(el.value, el.type)\n collectVariables(el.children, vars)\n }\n })\n}\n\ninterface IsStructurallySameResult {\n error?: Error\n success: boolean\n}\n\n/**\n * Check if 2 ASTs are structurally the same. This primarily means that\n * they have the same variables with the same type\n * @param a\n * @param b\n * @returns\n */\nexport function isStructurallySame(\n a: MessageFormatElement[],\n b: MessageFormatElement[]\n): IsStructurallySameResult {\n const aVars = new Map<string, TYPE>()\n const bVars = new Map<string, TYPE>()\n collectVariables(a, aVars)\n collectVariables(b, bVars)\n\n if (aVars.size !== bVars.size) {\n return {\n success: false,\n error: new Error(\n `Different number of variables: [${Array.from(aVars.keys()).join(', ')}] vs [${Array.from(bVars.keys()).join(', ')}]`\n ),\n }\n }\n\n return Array.from(aVars.entries()).reduce<IsStructurallySameResult>(\n (result, [key, type]) => {\n if (!result.success) {\n return result\n }\n const bType = bVars.get(key)\n if (bType == null) {\n return {\n success: false,\n error: new Error(`Missing variable ${key} in message`),\n }\n }\n if (bType !== type) {\n return {\n success: false,\n error: new Error(\n `Variable ${key} has conflicting types: ${TYPE[type]} vs ${TYPE[bType]}`\n ),\n }\n }\n return result\n },\n {success: true}\n )\n}\n","export function parse(): void {\n throw new Error(\n \"You're trying to format an uncompiled message with react-intl without parser, please import from 'react-intl' instead\"\n )\n}\nexport * from '#packages/icu-messageformat-parser/types.js'\nexport const _Parser = undefined\nexport {isStructurallySame} from '#packages/icu-messageformat-parser/manipulator.js'\n"],"mappings":"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"} | ||
| {"version":3,"file":"no-parser.js","names":[],"sources":["../types.ts","../manipulator.ts","../no-parser.ts"],"sourcesContent":["import type {NumberFormatOptions} from '#packages/ecma402-abstract/types/number.js'\nimport {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\n\nexport interface ExtendedNumberFormatOptions extends NumberFormatOptions {\n scale?: number\n}\n\nexport enum TYPE {\n /**\n * Raw text\n */\n literal,\n /**\n * Variable w/o any format, e.g `var` in `this is a {var}`\n */\n argument,\n /**\n * Variable w/ number format\n */\n number,\n /**\n * Variable w/ date format\n */\n date,\n /**\n * Variable w/ time format\n */\n time,\n /**\n * Variable w/ select format\n */\n select,\n /**\n * Variable w/ plural format\n */\n plural,\n /**\n * Only possible within plural argument.\n * This is the `#` symbol that will be substituted with the count.\n */\n pound,\n /**\n * XML-like tag\n */\n tag,\n}\n\nexport enum SKELETON_TYPE {\n number,\n dateTime,\n}\n\nexport interface LocationDetails {\n offset: number\n line: number\n column: number\n}\nexport interface Location {\n start: LocationDetails\n end: LocationDetails\n}\n\nexport interface BaseElement<T extends TYPE> {\n type: T\n value: string\n location?: Location\n}\n\nexport type LiteralElement = BaseElement<TYPE.literal>\nexport type ArgumentElement = BaseElement<TYPE.argument>\nexport interface TagElement extends BaseElement<TYPE.tag> {\n children: MessageFormatElement[]\n}\n\nexport interface SimpleFormatElement<\n T extends TYPE,\n S extends Skeleton,\n> extends BaseElement<T> {\n style?: string | S | null\n}\n\nexport type NumberElement = SimpleFormatElement<TYPE.number, NumberSkeleton>\nexport type DateElement = SimpleFormatElement<TYPE.date, DateTimeSkeleton>\nexport type TimeElement = SimpleFormatElement<TYPE.time, DateTimeSkeleton>\n\nexport type ValidPluralRule =\n | 'zero'\n | 'one'\n | 'two'\n | 'few'\n | 'many'\n | 'other'\n | string\n\nexport interface PluralOrSelectOption {\n value: MessageFormatElement[]\n location?: Location\n}\n\nexport interface SelectElement extends BaseElement<TYPE.select> {\n options: Record<string, PluralOrSelectOption>\n}\n\nexport interface PluralElement extends BaseElement<TYPE.plural> {\n options: Record<ValidPluralRule, PluralOrSelectOption>\n offset: number\n pluralType: Intl.PluralRulesOptions['type']\n}\n\nexport interface PoundElement {\n type: TYPE.pound\n location?: Location\n}\n\nexport type MessageFormatElement =\n | ArgumentElement\n | DateElement\n | LiteralElement\n | NumberElement\n | PluralElement\n | PoundElement\n | SelectElement\n | TagElement\n | TimeElement\n\nexport interface NumberSkeleton {\n type: SKELETON_TYPE.number\n tokens: NumberSkeletonToken[]\n location?: Location\n parsedOptions: ExtendedNumberFormatOptions\n}\n\nexport interface DateTimeSkeleton {\n type: SKELETON_TYPE.dateTime\n pattern: string\n location?: Location\n parsedOptions: Intl.DateTimeFormatOptions\n}\n\nexport type Skeleton = NumberSkeleton | DateTimeSkeleton\n\n/**\n * Type Guards\n */\nexport function isLiteralElement(\n el: MessageFormatElement\n): el is LiteralElement {\n return el.type === TYPE.literal\n}\nexport function isArgumentElement(\n el: MessageFormatElement\n): el is ArgumentElement {\n return el.type === TYPE.argument\n}\nexport function isNumberElement(el: MessageFormatElement): el is NumberElement {\n return el.type === TYPE.number\n}\nexport function isDateElement(el: MessageFormatElement): el is DateElement {\n return el.type === TYPE.date\n}\nexport function isTimeElement(el: MessageFormatElement): el is TimeElement {\n return el.type === TYPE.time\n}\nexport function isSelectElement(el: MessageFormatElement): el is SelectElement {\n return el.type === TYPE.select\n}\nexport function isPluralElement(el: MessageFormatElement): el is PluralElement {\n return el.type === TYPE.plural\n}\nexport function isPoundElement(el: MessageFormatElement): el is PoundElement {\n return el.type === TYPE.pound\n}\nexport function isTagElement(el: MessageFormatElement): el is TagElement {\n return el.type === TYPE.tag\n}\nexport function isNumberSkeleton(\n el: NumberElement['style'] | Skeleton\n): el is NumberSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number)\n}\nexport function isDateTimeSkeleton(\n el?: DateElement['style'] | TimeElement['style'] | Skeleton\n): el is DateTimeSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime)\n}\n\nexport function createLiteralElement(value: string): LiteralElement {\n return {\n type: TYPE.literal,\n value,\n }\n}\n\nexport function createNumberElement(\n value: string,\n style?: string | null\n): NumberElement {\n return {\n type: TYPE.number,\n value,\n style,\n }\n}\n","import {\n isArgumentElement,\n isDateElement,\n isNumberElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n isTimeElement,\n type MessageFormatElement,\n type PluralElement,\n type PluralOrSelectOption,\n type SelectElement,\n TYPE,\n} from '#packages/icu-messageformat-parser/types.js'\n\nfunction cloneDeep<T>(obj: T): T {\n if (Array.isArray(obj)) {\n // @ts-expect-error meh\n return obj.map(cloneDeep)\n }\n if (obj !== null && typeof obj === 'object') {\n // @ts-expect-error meh\n return Object.keys(obj).reduce((cloned, k) => {\n // @ts-expect-error meh\n cloned[k] = cloneDeep(obj[k])\n return cloned\n }, {})\n }\n return obj\n}\n\n/**\n * Replace pound elements with number elements referencing the given variable.\n * This is needed when nesting plurals - the # in the outer plural should become\n * an explicit variable reference when nested inside another plural.\n * GH #4202\n */\nfunction replacePoundWithArgument(\n ast: MessageFormatElement[],\n variableName: string\n): MessageFormatElement[] {\n return ast.map(el => {\n if (isPoundElement(el)) {\n // Replace # with {variableName, number}\n return {\n type: TYPE.number,\n value: variableName,\n style: null,\n location: el.location,\n }\n }\n if (isPluralElement(el) || isSelectElement(el)) {\n // Recursively process options\n const newOptions: Record<string, PluralOrSelectOption> = {}\n for (const key of Object.keys(el.options)) {\n newOptions[key] = {\n value: replacePoundWithArgument(el.options[key].value, variableName),\n }\n }\n return {...el, options: newOptions}\n }\n if (isTagElement(el)) {\n return {\n ...el,\n children: replacePoundWithArgument(el.children, variableName),\n }\n }\n return el\n })\n}\n\nfunction hoistPluralOrSelectElement(\n ast: MessageFormatElement[],\n el: PluralElement | SelectElement,\n positionToInject: number\n) {\n // pull this out of the ast and move it to the top\n const cloned = cloneDeep(el)\n const {options} = cloned\n\n // GH #4202: Check if there are other plural/select elements after this one\n const afterElements = ast.slice(positionToInject + 1)\n const hasSubsequentPluralOrSelect = afterElements.some(\n isPluralOrSelectElement\n )\n\n cloned.options = Object.keys(options).reduce(\n (all: Record<string, PluralOrSelectOption>, k) => {\n let optionValue = options[k].value\n\n // GH #4202: If there are subsequent plurals/selects and this is a plural,\n // replace # with explicit variable reference to avoid ambiguity\n if (hasSubsequentPluralOrSelect && isPluralElement(el)) {\n optionValue = replacePoundWithArgument(optionValue, el.value)\n }\n\n const newValue = hoistSelectors([\n ...ast.slice(0, positionToInject),\n ...optionValue,\n ...afterElements,\n ])\n all[k] = {\n value: newValue,\n }\n return all\n },\n {}\n )\n return cloned\n}\n\nfunction isPluralOrSelectElement(\n el: MessageFormatElement\n): el is PluralElement | SelectElement {\n return isPluralElement(el) || isSelectElement(el)\n}\n\nfunction findPluralOrSelectElement(ast: MessageFormatElement[]): boolean {\n return !!ast.find(el => {\n if (isPluralOrSelectElement(el)) {\n return true\n }\n if (isTagElement(el)) {\n return findPluralOrSelectElement(el.children)\n }\n return false\n })\n}\n\n/**\n * Hoist all selectors to the beginning of the AST & flatten the\n * resulting options. E.g:\n * \"I have {count, plural, one{a dog} other{many dogs}}\"\n * becomes \"{count, plural, one{I have a dog} other{I have many dogs}}\".\n * If there are multiple selectors, the order of which one is hoisted 1st\n * is non-deterministic.\n * The goal is to provide as many full sentences as possible since fragmented\n * sentences are not translator-friendly\n * @param ast AST\n */\nexport function hoistSelectors(\n ast: MessageFormatElement[]\n): MessageFormatElement[] {\n for (let i = 0; i < ast.length; i++) {\n const el = ast[i]\n if (isPluralOrSelectElement(el)) {\n return [hoistPluralOrSelectElement(ast, el, i)]\n }\n if (isTagElement(el) && findPluralOrSelectElement([el])) {\n throw new Error(\n 'Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option'\n )\n }\n }\n return ast\n}\n\n/**\n * Collect all variables in an AST to Record<string, TYPE>\n * @param ast AST to collect variables from\n * @param vars Record of variable name to variable type\n */\nfunction collectVariables(\n ast: MessageFormatElement[],\n vars: Map<string, TYPE> = new Map<string, TYPE>()\n): void {\n ast.forEach(el => {\n if (\n isArgumentElement(el) ||\n isDateElement(el) ||\n isTimeElement(el) ||\n isNumberElement(el)\n ) {\n // If the variable was already registered as a plural/select, it's normal\n // for it to also appear inside as number/date/time/argument — not a conflict.\n if (vars.has(el.value)) {\n const existingType = vars.get(el.value)!\n if (\n existingType !== el.type &&\n existingType !== TYPE.plural &&\n existingType !== TYPE.select\n ) {\n throw new Error(`Variable ${el.value} has conflicting types`)\n }\n } else {\n vars.set(el.value, el.type)\n }\n }\n\n if (isPluralElement(el) || isSelectElement(el)) {\n vars.set(el.value, el.type)\n Object.keys(el.options).forEach(k => {\n collectVariables(el.options[k].value, vars)\n })\n }\n\n if (isTagElement(el)) {\n vars.set(el.value, el.type)\n collectVariables(el.children, vars)\n }\n })\n}\n\ninterface IsStructurallySameResult {\n error?: Error\n success: boolean\n}\n\n/**\n * Check if 2 ASTs are structurally the same. This primarily means that\n * they have the same variables with the same type\n * @param a\n * @param b\n * @returns\n */\nexport function isStructurallySame(\n a: MessageFormatElement[],\n b: MessageFormatElement[]\n): IsStructurallySameResult {\n const aVars = new Map<string, TYPE>()\n const bVars = new Map<string, TYPE>()\n collectVariables(a, aVars)\n collectVariables(b, bVars)\n\n if (aVars.size !== bVars.size) {\n return {\n success: false,\n error: new Error(\n `Different number of variables: [${Array.from(aVars.keys()).join(', ')}] vs [${Array.from(bVars.keys()).join(', ')}]`\n ),\n }\n }\n\n return Array.from(aVars.entries()).reduce<IsStructurallySameResult>(\n (result, [key, type]) => {\n if (!result.success) {\n return result\n }\n const bType = bVars.get(key)\n if (bType == null) {\n return {\n success: false,\n error: new Error(`Missing variable ${key} in message`),\n }\n }\n if (bType !== type) {\n return {\n success: false,\n error: new Error(\n `Variable ${key} has conflicting types: ${TYPE[type]} vs ${TYPE[bType]}`\n ),\n }\n }\n return result\n },\n {success: true}\n )\n}\n","export function parse(): void {\n throw new Error(\n \"You're trying to format an uncompiled message with react-intl without parser, please import from 'react-intl' instead\"\n )\n}\nexport * from '#packages/icu-messageformat-parser/types.js'\nexport const _Parser = undefined\nexport {isStructurallySame} from '#packages/icu-messageformat-parser/manipulator.js'\n"],"mappings":"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"} |
+1
-1
| { | ||
| "name": "@formatjs/icu-messageformat-parser", | ||
| "version": "3.5.9", | ||
| "version": "3.5.10", | ||
| "license": "MIT", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+54
-19
@@ -42,7 +42,2 @@ //#region packages/icu-messageformat-parser/types.ts | ||
| }({}); | ||
| let SKELETON_TYPE = /* @__PURE__ */ function(SKELETON_TYPE) { | ||
| SKELETON_TYPE[SKELETON_TYPE["number"] = 0] = "number"; | ||
| SKELETON_TYPE[SKELETON_TYPE["dateTime"] = 1] = "dateTime"; | ||
| return SKELETON_TYPE; | ||
| }({}); | ||
| /** | ||
@@ -52,27 +47,27 @@ * Type Guards | ||
| function isLiteralElement(el) { | ||
| return el.type === TYPE.literal; | ||
| return el.type === 0; | ||
| } | ||
| function isArgumentElement(el) { | ||
| return el.type === TYPE.argument; | ||
| return el.type === 1; | ||
| } | ||
| function isNumberElement(el) { | ||
| return el.type === TYPE.number; | ||
| return el.type === 2; | ||
| } | ||
| function isDateElement(el) { | ||
| return el.type === TYPE.date; | ||
| return el.type === 3; | ||
| } | ||
| function isTimeElement(el) { | ||
| return el.type === TYPE.time; | ||
| return el.type === 4; | ||
| } | ||
| function isSelectElement(el) { | ||
| return el.type === TYPE.select; | ||
| return el.type === 5; | ||
| } | ||
| function isPluralElement(el) { | ||
| return el.type === TYPE.plural; | ||
| return el.type === 6; | ||
| } | ||
| function isPoundElement(el) { | ||
| return el.type === TYPE.pound; | ||
| return el.type === 7; | ||
| } | ||
| function isTagElement(el) { | ||
| return el.type === TYPE.tag; | ||
| return el.type === 8; | ||
| } | ||
@@ -98,5 +93,46 @@ //#endregion | ||
| } | ||
| function printEscapedMessage(message) { | ||
| return message.replace(/([{}](?:[\s\S]*[{}])?)/, `'$1'`); | ||
| function quoteSyntaxToken(token) { | ||
| return `'${token.split("'").join("''")}'`; | ||
| } | ||
| function isAlpha(ch) { | ||
| if (!ch) return false; | ||
| const code = ch.charCodeAt(0); | ||
| return code >= 65 && code <= 90 || code >= 97 && code <= 122; | ||
| } | ||
| function isTagSyntaxStart(message, index) { | ||
| if (message[index] !== "<") return false; | ||
| const next = message[index + 1]; | ||
| return next === "/" || isAlpha(next); | ||
| } | ||
| function findTagSyntaxEnd(message, index) { | ||
| const closingIndex = message.indexOf(">", index + 1); | ||
| return closingIndex === -1 ? message.length : closingIndex + 1; | ||
| } | ||
| function findBraceSyntaxEnd(message, index) { | ||
| const closingIndex = message.indexOf("}", index + 1); | ||
| return closingIndex === -1 ? index + 1 : closingIndex + 1; | ||
| } | ||
| function printEscapedMessage(message, isInPlural = false) { | ||
| let result = ""; | ||
| let literalStart = 0; | ||
| function quoteToken(start, end) { | ||
| result += message.slice(literalStart, start); | ||
| result += quoteSyntaxToken(message.slice(start, end)); | ||
| literalStart = end; | ||
| } | ||
| for (let i = 0; i < message.length; i++) { | ||
| const ch = message[i]; | ||
| if (ch === "{") { | ||
| const end = findBraceSyntaxEnd(message, i); | ||
| quoteToken(i, end); | ||
| i = end - 1; | ||
| } else if (ch === "}") quoteToken(i, i + 1); | ||
| else if (isTagSyntaxStart(message, i)) { | ||
| const end = findTagSyntaxEnd(message, i); | ||
| quoteToken(i, end); | ||
| i = end - 1; | ||
| } else if (isInPlural && ch === "#") quoteToken(i, i + 1); | ||
| } | ||
| return result + message.slice(literalStart); | ||
| } | ||
| function printLiteralElement({ value }, isInPlural, isFirstEl, isLastEl) { | ||
@@ -106,4 +142,3 @@ let escaped = value; | ||
| if (!isLastEl && escaped[escaped.length - 1] === `'`) escaped = `${escaped.slice(0, escaped.length - 1)}''`; | ||
| escaped = printEscapedMessage(escaped); | ||
| return isInPlural ? escaped.replace("#", "'#'") : escaped; | ||
| return printEscapedMessage(escaped, isInPlural); | ||
| } | ||
@@ -122,3 +157,3 @@ function printArgumentElement({ value }) { | ||
| if (typeof style === "string") return printEscapedMessage(style); | ||
| else if (style.type === SKELETON_TYPE.dateTime) return `::${printDateTimeSkeleton(style)}`; | ||
| else if (style.type === 1) return `::${printDateTimeSkeleton(style)}`; | ||
| else return `::${style.tokens.map(printNumberSkeletonToken).join(" ")}`; | ||
@@ -125,0 +160,0 @@ } |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"printer.js","names":[],"sources":["../types.ts","../printer.ts"],"sourcesContent":["import type {NumberFormatOptions} from '#packages/ecma402-abstract/types/number.js'\nimport {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\n\nexport interface ExtendedNumberFormatOptions extends NumberFormatOptions {\n scale?: number\n}\n\nexport enum TYPE {\n /**\n * Raw text\n */\n literal,\n /**\n * Variable w/o any format, e.g `var` in `this is a {var}`\n */\n argument,\n /**\n * Variable w/ number format\n */\n number,\n /**\n * Variable w/ date format\n */\n date,\n /**\n * Variable w/ time format\n */\n time,\n /**\n * Variable w/ select format\n */\n select,\n /**\n * Variable w/ plural format\n */\n plural,\n /**\n * Only possible within plural argument.\n * This is the `#` symbol that will be substituted with the count.\n */\n pound,\n /**\n * XML-like tag\n */\n tag,\n}\n\nexport enum SKELETON_TYPE {\n number,\n dateTime,\n}\n\nexport interface LocationDetails {\n offset: number\n line: number\n column: number\n}\nexport interface Location {\n start: LocationDetails\n end: LocationDetails\n}\n\nexport interface BaseElement<T extends TYPE> {\n type: T\n value: string\n location?: Location\n}\n\nexport type LiteralElement = BaseElement<TYPE.literal>\nexport type ArgumentElement = BaseElement<TYPE.argument>\nexport interface TagElement extends BaseElement<TYPE.tag> {\n children: MessageFormatElement[]\n}\n\nexport interface SimpleFormatElement<\n T extends TYPE,\n S extends Skeleton,\n> extends BaseElement<T> {\n style?: string | S | null\n}\n\nexport type NumberElement = SimpleFormatElement<TYPE.number, NumberSkeleton>\nexport type DateElement = SimpleFormatElement<TYPE.date, DateTimeSkeleton>\nexport type TimeElement = SimpleFormatElement<TYPE.time, DateTimeSkeleton>\n\nexport type ValidPluralRule =\n | 'zero'\n | 'one'\n | 'two'\n | 'few'\n | 'many'\n | 'other'\n | string\n\nexport interface PluralOrSelectOption {\n value: MessageFormatElement[]\n location?: Location\n}\n\nexport interface SelectElement extends BaseElement<TYPE.select> {\n options: Record<string, PluralOrSelectOption>\n}\n\nexport interface PluralElement extends BaseElement<TYPE.plural> {\n options: Record<ValidPluralRule, PluralOrSelectOption>\n offset: number\n pluralType: Intl.PluralRulesOptions['type']\n}\n\nexport interface PoundElement {\n type: TYPE.pound\n location?: Location\n}\n\nexport type MessageFormatElement =\n | ArgumentElement\n | DateElement\n | LiteralElement\n | NumberElement\n | PluralElement\n | PoundElement\n | SelectElement\n | TagElement\n | TimeElement\n\nexport interface NumberSkeleton {\n type: SKELETON_TYPE.number\n tokens: NumberSkeletonToken[]\n location?: Location\n parsedOptions: ExtendedNumberFormatOptions\n}\n\nexport interface DateTimeSkeleton {\n type: SKELETON_TYPE.dateTime\n pattern: string\n location?: Location\n parsedOptions: Intl.DateTimeFormatOptions\n}\n\nexport type Skeleton = NumberSkeleton | DateTimeSkeleton\n\n/**\n * Type Guards\n */\nexport function isLiteralElement(\n el: MessageFormatElement\n): el is LiteralElement {\n return el.type === TYPE.literal\n}\nexport function isArgumentElement(\n el: MessageFormatElement\n): el is ArgumentElement {\n return el.type === TYPE.argument\n}\nexport function isNumberElement(el: MessageFormatElement): el is NumberElement {\n return el.type === TYPE.number\n}\nexport function isDateElement(el: MessageFormatElement): el is DateElement {\n return el.type === TYPE.date\n}\nexport function isTimeElement(el: MessageFormatElement): el is TimeElement {\n return el.type === TYPE.time\n}\nexport function isSelectElement(el: MessageFormatElement): el is SelectElement {\n return el.type === TYPE.select\n}\nexport function isPluralElement(el: MessageFormatElement): el is PluralElement {\n return el.type === TYPE.plural\n}\nexport function isPoundElement(el: MessageFormatElement): el is PoundElement {\n return el.type === TYPE.pound\n}\nexport function isTagElement(el: MessageFormatElement): el is TagElement {\n return el.type === TYPE.tag\n}\nexport function isNumberSkeleton(\n el: NumberElement['style'] | Skeleton\n): el is NumberSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number)\n}\nexport function isDateTimeSkeleton(\n el?: DateElement['style'] | TimeElement['style'] | Skeleton\n): el is DateTimeSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime)\n}\n\nexport function createLiteralElement(value: string): LiteralElement {\n return {\n type: TYPE.literal,\n value,\n }\n}\n\nexport function createNumberElement(\n value: string,\n style?: string | null\n): NumberElement {\n return {\n type: TYPE.number,\n value,\n style,\n }\n}\n","import {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\nimport {\n type ArgumentElement,\n type DateElement,\n type DateTimeSkeleton,\n isArgumentElement,\n isDateElement,\n isLiteralElement,\n isNumberElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n isTimeElement,\n type LiteralElement,\n type MessageFormatElement,\n type NumberElement,\n type PluralElement,\n type SelectElement,\n type Skeleton,\n SKELETON_TYPE,\n type TagElement,\n type TimeElement,\n TYPE,\n} from '#packages/icu-messageformat-parser/types.js'\n\nexport function printAST(ast: MessageFormatElement[]): string {\n return doPrintAST(ast, false)\n}\n\nexport function doPrintAST(\n ast: MessageFormatElement[],\n isInPlural: boolean\n): string {\n const printedNodes = ast.map((el, i) => {\n if (isLiteralElement(el)) {\n return printLiteralElement(el, isInPlural, i === 0, i === ast.length - 1)\n }\n\n if (isArgumentElement(el)) {\n return printArgumentElement(el)\n }\n if (isDateElement(el) || isTimeElement(el) || isNumberElement(el)) {\n return printSimpleFormatElement(el)\n }\n\n if (isPluralElement(el)) {\n return printPluralElement(el)\n }\n\n if (isSelectElement(el)) {\n return printSelectElement(el)\n }\n\n if (isPoundElement(el)) {\n return '#'\n }\n if (isTagElement(el)) {\n return printTagElement(el)\n }\n })\n\n return printedNodes.join('')\n}\n\nfunction printTagElement(el: TagElement): string {\n return `<${el.value}>${printAST(el.children)}</${el.value}>`\n}\n\nfunction printEscapedMessage(message: string): string {\n return message.replace(/([{}](?:[\\s\\S]*[{}])?)/, `'$1'`)\n}\n\nfunction printLiteralElement(\n {value}: LiteralElement,\n isInPlural: boolean,\n isFirstEl: boolean,\n isLastEl: boolean\n) {\n let escaped = value\n // If this literal starts with a ' and its not the 1st node, this means the node before it is non-literal\n // and the `'` needs to be unescaped\n if (!isFirstEl && escaped[0] === `'`) {\n escaped = `''${escaped.slice(1)}`\n }\n // Same logic but for last el\n if (!isLastEl && escaped[escaped.length - 1] === `'`) {\n escaped = `${escaped.slice(0, escaped.length - 1)}''`\n }\n escaped = printEscapedMessage(escaped)\n return isInPlural ? escaped.replace('#', \"'#'\") : escaped\n}\n\nfunction printArgumentElement({value}: ArgumentElement) {\n return `{${value}}`\n}\n\nfunction printSimpleFormatElement(\n el: DateElement | TimeElement | NumberElement\n) {\n return `{${el.value}, ${TYPE[el.type]}${\n el.style ? `, ${printArgumentStyle(el.style)}` : ''\n }}`\n}\n\nfunction printNumberSkeletonToken(token: NumberSkeletonToken): string {\n const {stem, options} = token\n return options.length === 0\n ? stem\n : `${stem}${options.map(o => `/${o}`).join('')}`\n}\n\nfunction printArgumentStyle(style: string | Skeleton) {\n if (typeof style === 'string') {\n return printEscapedMessage(style)\n } else if (style.type === SKELETON_TYPE.dateTime) {\n return `::${printDateTimeSkeleton(style)}`\n } else {\n return `::${style.tokens.map(printNumberSkeletonToken).join(' ')}`\n }\n}\n\nexport function printDateTimeSkeleton(style: DateTimeSkeleton): string {\n return style.pattern\n}\n\nfunction printSelectElement(el: SelectElement) {\n const msg = [\n el.value,\n 'select',\n Object.keys(el.options)\n .map(id => `${id}{${doPrintAST(el.options[id].value, false)}}`)\n .join(' '),\n ].join(',')\n return `{${msg}}`\n}\n\nfunction printPluralElement(el: PluralElement) {\n const type = el.pluralType === 'cardinal' ? 'plural' : 'selectordinal'\n const msg = [\n el.value,\n type,\n [\n el.offset ? `offset:${el.offset}` : '',\n ...Object.keys(el.options).map(\n id => `${id}{${doPrintAST(el.options[id].value, true)}}`\n ),\n ]\n .filter(Boolean)\n .join(' '),\n ].join(',')\n return `{${msg}}`\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"printer.js","names":[],"sources":["../types.ts","../printer.ts"],"sourcesContent":["import type {NumberFormatOptions} from '#packages/ecma402-abstract/types/number.js'\nimport {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\n\nexport interface ExtendedNumberFormatOptions extends NumberFormatOptions {\n scale?: number\n}\n\nexport enum TYPE {\n /**\n * Raw text\n */\n literal,\n /**\n * Variable w/o any format, e.g `var` in `this is a {var}`\n */\n argument,\n /**\n * Variable w/ number format\n */\n number,\n /**\n * Variable w/ date format\n */\n date,\n /**\n * Variable w/ time format\n */\n time,\n /**\n * Variable w/ select format\n */\n select,\n /**\n * Variable w/ plural format\n */\n plural,\n /**\n * Only possible within plural argument.\n * This is the `#` symbol that will be substituted with the count.\n */\n pound,\n /**\n * XML-like tag\n */\n tag,\n}\n\nexport enum SKELETON_TYPE {\n number,\n dateTime,\n}\n\nexport interface LocationDetails {\n offset: number\n line: number\n column: number\n}\nexport interface Location {\n start: LocationDetails\n end: LocationDetails\n}\n\nexport interface BaseElement<T extends TYPE> {\n type: T\n value: string\n location?: Location\n}\n\nexport type LiteralElement = BaseElement<TYPE.literal>\nexport type ArgumentElement = BaseElement<TYPE.argument>\nexport interface TagElement extends BaseElement<TYPE.tag> {\n children: MessageFormatElement[]\n}\n\nexport interface SimpleFormatElement<\n T extends TYPE,\n S extends Skeleton,\n> extends BaseElement<T> {\n style?: string | S | null\n}\n\nexport type NumberElement = SimpleFormatElement<TYPE.number, NumberSkeleton>\nexport type DateElement = SimpleFormatElement<TYPE.date, DateTimeSkeleton>\nexport type TimeElement = SimpleFormatElement<TYPE.time, DateTimeSkeleton>\n\nexport type ValidPluralRule =\n | 'zero'\n | 'one'\n | 'two'\n | 'few'\n | 'many'\n | 'other'\n | string\n\nexport interface PluralOrSelectOption {\n value: MessageFormatElement[]\n location?: Location\n}\n\nexport interface SelectElement extends BaseElement<TYPE.select> {\n options: Record<string, PluralOrSelectOption>\n}\n\nexport interface PluralElement extends BaseElement<TYPE.plural> {\n options: Record<ValidPluralRule, PluralOrSelectOption>\n offset: number\n pluralType: Intl.PluralRulesOptions['type']\n}\n\nexport interface PoundElement {\n type: TYPE.pound\n location?: Location\n}\n\nexport type MessageFormatElement =\n | ArgumentElement\n | DateElement\n | LiteralElement\n | NumberElement\n | PluralElement\n | PoundElement\n | SelectElement\n | TagElement\n | TimeElement\n\nexport interface NumberSkeleton {\n type: SKELETON_TYPE.number\n tokens: NumberSkeletonToken[]\n location?: Location\n parsedOptions: ExtendedNumberFormatOptions\n}\n\nexport interface DateTimeSkeleton {\n type: SKELETON_TYPE.dateTime\n pattern: string\n location?: Location\n parsedOptions: Intl.DateTimeFormatOptions\n}\n\nexport type Skeleton = NumberSkeleton | DateTimeSkeleton\n\n/**\n * Type Guards\n */\nexport function isLiteralElement(\n el: MessageFormatElement\n): el is LiteralElement {\n return el.type === TYPE.literal\n}\nexport function isArgumentElement(\n el: MessageFormatElement\n): el is ArgumentElement {\n return el.type === TYPE.argument\n}\nexport function isNumberElement(el: MessageFormatElement): el is NumberElement {\n return el.type === TYPE.number\n}\nexport function isDateElement(el: MessageFormatElement): el is DateElement {\n return el.type === TYPE.date\n}\nexport function isTimeElement(el: MessageFormatElement): el is TimeElement {\n return el.type === TYPE.time\n}\nexport function isSelectElement(el: MessageFormatElement): el is SelectElement {\n return el.type === TYPE.select\n}\nexport function isPluralElement(el: MessageFormatElement): el is PluralElement {\n return el.type === TYPE.plural\n}\nexport function isPoundElement(el: MessageFormatElement): el is PoundElement {\n return el.type === TYPE.pound\n}\nexport function isTagElement(el: MessageFormatElement): el is TagElement {\n return el.type === TYPE.tag\n}\nexport function isNumberSkeleton(\n el: NumberElement['style'] | Skeleton\n): el is NumberSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number)\n}\nexport function isDateTimeSkeleton(\n el?: DateElement['style'] | TimeElement['style'] | Skeleton\n): el is DateTimeSkeleton {\n return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime)\n}\n\nexport function createLiteralElement(value: string): LiteralElement {\n return {\n type: TYPE.literal,\n value,\n }\n}\n\nexport function createNumberElement(\n value: string,\n style?: string | null\n): NumberElement {\n return {\n type: TYPE.number,\n value,\n style,\n }\n}\n","import {type NumberSkeletonToken} from '@formatjs/icu-skeleton-parser'\nimport {\n type ArgumentElement,\n type DateElement,\n type DateTimeSkeleton,\n isArgumentElement,\n isDateElement,\n isLiteralElement,\n isNumberElement,\n isPluralElement,\n isPoundElement,\n isSelectElement,\n isTagElement,\n isTimeElement,\n type LiteralElement,\n type MessageFormatElement,\n type NumberElement,\n type PluralElement,\n type SelectElement,\n type Skeleton,\n SKELETON_TYPE,\n type TagElement,\n type TimeElement,\n TYPE,\n} from '#packages/icu-messageformat-parser/types.js'\n\nexport function printAST(ast: MessageFormatElement[]): string {\n return doPrintAST(ast, false)\n}\n\nexport function doPrintAST(\n ast: MessageFormatElement[],\n isInPlural: boolean\n): string {\n const printedNodes = ast.map((el, i) => {\n if (isLiteralElement(el)) {\n return printLiteralElement(el, isInPlural, i === 0, i === ast.length - 1)\n }\n\n if (isArgumentElement(el)) {\n return printArgumentElement(el)\n }\n if (isDateElement(el) || isTimeElement(el) || isNumberElement(el)) {\n return printSimpleFormatElement(el)\n }\n\n if (isPluralElement(el)) {\n return printPluralElement(el)\n }\n\n if (isSelectElement(el)) {\n return printSelectElement(el)\n }\n\n if (isPoundElement(el)) {\n return '#'\n }\n if (isTagElement(el)) {\n return printTagElement(el)\n }\n })\n\n return printedNodes.join('')\n}\n\nfunction printTagElement(el: TagElement): string {\n return `<${el.value}>${printAST(el.children)}</${el.value}>`\n}\n\nfunction quoteSyntaxToken(token: string): string {\n return `'${token.split(\"'\").join(\"''\")}'`\n}\n\nfunction isAlpha(ch: string | undefined): boolean {\n if (!ch) {\n return false\n }\n const code = ch.charCodeAt(0)\n return (code >= 65 && code <= 90) || (code >= 97 && code <= 122)\n}\n\nfunction isTagSyntaxStart(message: string, index: number): boolean {\n if (message[index] !== '<') {\n return false\n }\n const next = message[index + 1]\n return next === '/' || isAlpha(next)\n}\n\nfunction findTagSyntaxEnd(message: string, index: number): number {\n const closingIndex = message.indexOf('>', index + 1)\n return closingIndex === -1 ? message.length : closingIndex + 1\n}\n\nfunction findBraceSyntaxEnd(message: string, index: number): number {\n const closingIndex = message.indexOf('}', index + 1)\n return closingIndex === -1 ? index + 1 : closingIndex + 1\n}\n\nfunction printEscapedMessage(message: string, isInPlural = false): string {\n let result = ''\n let literalStart = 0\n\n function quoteToken(start: number, end: number) {\n result += message.slice(literalStart, start)\n result += quoteSyntaxToken(message.slice(start, end))\n literalStart = end\n }\n\n for (let i = 0; i < message.length; i++) {\n const ch = message[i]\n if (ch === '{') {\n const end = findBraceSyntaxEnd(message, i)\n quoteToken(i, end)\n i = end - 1\n } else if (ch === '}') {\n quoteToken(i, i + 1)\n } else if (isTagSyntaxStart(message, i)) {\n const end = findTagSyntaxEnd(message, i)\n quoteToken(i, end)\n i = end - 1\n } else if (isInPlural && ch === '#') {\n quoteToken(i, i + 1)\n }\n }\n\n return result + message.slice(literalStart)\n}\n\nfunction printLiteralElement(\n {value}: LiteralElement,\n isInPlural: boolean,\n isFirstEl: boolean,\n isLastEl: boolean\n) {\n let escaped = value\n // If this literal starts with a ' and its not the 1st node, this means the node before it is non-literal\n // and the `'` needs to be unescaped\n if (!isFirstEl && escaped[0] === `'`) {\n escaped = `''${escaped.slice(1)}`\n }\n // Same logic but for last el\n if (!isLastEl && escaped[escaped.length - 1] === `'`) {\n escaped = `${escaped.slice(0, escaped.length - 1)}''`\n }\n return printEscapedMessage(escaped, isInPlural)\n}\n\nfunction printArgumentElement({value}: ArgumentElement) {\n return `{${value}}`\n}\n\nfunction printSimpleFormatElement(\n el: DateElement | TimeElement | NumberElement\n) {\n return `{${el.value}, ${TYPE[el.type]}${\n el.style ? `, ${printArgumentStyle(el.style)}` : ''\n }}`\n}\n\nfunction printNumberSkeletonToken(token: NumberSkeletonToken): string {\n const {stem, options} = token\n return options.length === 0\n ? stem\n : `${stem}${options.map(o => `/${o}`).join('')}`\n}\n\nfunction printArgumentStyle(style: string | Skeleton) {\n if (typeof style === 'string') {\n return printEscapedMessage(style)\n } else if (style.type === SKELETON_TYPE.dateTime) {\n return `::${printDateTimeSkeleton(style)}`\n } else {\n return `::${style.tokens.map(printNumberSkeletonToken).join(' ')}`\n }\n}\n\nexport function printDateTimeSkeleton(style: DateTimeSkeleton): string {\n return style.pattern\n}\n\nfunction printSelectElement(el: SelectElement) {\n const msg = [\n el.value,\n 'select',\n Object.keys(el.options)\n .map(id => `${id}{${doPrintAST(el.options[id].value, false)}}`)\n .join(' '),\n ].join(',')\n return `{${msg}}`\n}\n\nfunction printPluralElement(el: PluralElement) {\n const type = el.pluralType === 'cardinal' ? 'plural' : 'selectordinal'\n const msg = [\n el.value,\n type,\n [\n el.offset ? `offset:${el.offset}` : '',\n ...Object.keys(el.options).map(\n id => `${id}{${doPrintAST(el.options[id].value, true)}}`\n ),\n ]\n .filter(Boolean)\n .join(' '),\n ].join(',')\n return `{${msg}}`\n}\n"],"mappings":"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"} |
Sorry, the diff of this file is too big to display
240173
0.9%3571
0.99%