Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

docs: document transformation output options #42

Merged
merged 3 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 112 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,88 @@ This function accepts file contents and parses it to a JavaScript value that wil

An object whose keys are message IDs and whose values are either parsing options for those messages or a resolver function that generates parsing options based on contextual information (such as module ID, message ID, and all messages).

#### **`onParseError`**

- **Type**: [`ParseErrorHandlingOption`](#parseerrorhandlingoption)
- **Default**: `undefined`

A method to handle any errors that may arise during the parsing of one of the messages.

#### **`pluginsWrapping`**

- **Type**: `boolean | WrappingOptions<PluginType>`
- **Type**: `boolean` `|` [`WrappingOptions<PluginType>`](#wrappingoptions)
- **Default**: `false`

Plugins wrapping enables additional hooks in compatible bundlers to prevent other plugins from transforming files that would be transformed by this plugin.

#### **`output`**

- **Type**: [`OutputOptions`](#outputoptions)
- **Default**: `{ format: 'module', type: 'ast' }`

Options that allow to configure the output of transformation.

---

### `ParseErrorHandlingOption`

- **Type**: `(context: `[`ParseErrorContext`](#parseerrorcontext)`) => MessageFormatElement[] | void`

Either a name of the built-in handler, or a custom method that will accept context and may return the fallback result, throw another error, or return nothing (`undefined`) to ignore the error.

Custom methods can access the built-in handlers using the context's `useBuiltinStrategy` method and are used solely for logging.

The following built-in handlers exist:

| Name | Description |
| ------------------------ | ------------------------------------------------ |
| `use-message-as-literal` | Uses the unparsed message contents as a literal. |
| `use-id-as-literal` | Uses the the message ID as a literal. |
| `use-empty-literal` | Uses a literal with an empty string. |
| `skip` | Ignore the error and skip the message. |

---

### `ParseErrorContext`

A read-only object containing information relevant to the parsing error, including the error itself.

#### **`moduleId`**

- **Type**: `string`

ID of the module that is being parsed.

#### **`messageId`**

- **Type**: `string`

ID of the message that cannot be parsed.

#### **`message`**

- **Type**: `string`

Message that cannot be parsed.

#### **`error`**

- **Type**: `unknown`

Error that occurred during the parsing.

#### **`parserOptions`**

- **Type**: `ParserOptions | undefined`

Parser options that were used to parse the message.

#### **`useBuiltinStrategy`**

- **Type**: `(name: ParseErrorHandlingStrategy) => MessageFormatElement[] | void`

Method used to call one of the built-in error handling strategies and return its result.

---

### `WrappingOptions`
Expand All @@ -180,6 +255,42 @@ Whether to extend the defaults with provided `wrappers` or overwrite them.

A map of wrapping functions that can be used to modify the behavior of plugins. The map is an object where each key is a plugin name and the value is a function that accepts a plugin object and a filter function, and mutates the plugin hooks to use the provided filter function.

---

### `OutputOptions`

Represents options for the transformation output.

#### **`format`**

- **Type**: `'module' | 'json' | ((messages: MessagesASTMap | MessagesMap) => string)`
- **Default**: `'module'`

Defines the format of the output file or provides a function that will encode input JavaScript object containing the messages into a string representing contents of the transformed file.

The following formats are supported:

| Name | Description |
| :--------: | :------------------------------------------------------------ |
| `'module'` | Outputs an ESM JavaScript module. |
| `'json'` | Outputs a JSON string that can be processed by other plugins. |

You can also provide a custom function that accepts either a `MessagesASTMap` or a `MessagesMap` and returns a string.

#### **`type`**

- **Type**: `'raw' | 'ast'`
- **Default**: `'ast'`

Defines what kind of output should be generated.

The following output types are supported:

| Name | Description |
| :-----: | :--------------------------------------------- |
| `'raw'` | Outputs the messages as is. |
| `'ast'` | Pre-parses the messages and outputs their AST. |

</details>

## Usage with other plugins
Expand Down
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './secondary-exports.ts'
export { plugin as icuMessages } from './plugin/index.ts'

export {
plugin as icuMessages,
type Options as PluginOptions,
} from './plugin/index.ts'
109 changes: 109 additions & 0 deletions src/parser/error-handling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
type MessageFormatElement,
createLiteralElement,
type ParserOptions,
} from '@formatjs/icu-messageformat-parser'

type ParseErrorHandlerResult = MessageFormatElement[] | undefined | void

/**
* A function that handles parsing error and either throws another error, or
* returns a fallback string or parse result.
*
* @param context Context containing the error, message ID and other relevant
* properties.
* @returns The fallback value or nothing.
*/
export type ParseErrorHandler = (
context: ParseErrorContext,
) => ParseErrorHandlerResult

export const builtinStrategies = {
/** @returns The original message as a literal. */
'use-message-as-literal'(ctx) {
return [createLiteralElement(ctx.message)]
},
/** @returns The message ID as a literal. */
'use-id-as-literal'(ctx) {
return [createLiteralElement(ctx.messageId)]
},
/** @returns Empty literal. */
'use-empty-literal'() {
return [createLiteralElement('')]
},
/** @returns `undefined`, which skips the string. */
skip() {
return undefined
},
} satisfies Record<string, ParseErrorHandler>

Object.setPrototypeOf(builtinStrategies, null)

type ParseErrorHandlingStrategy = keyof typeof builtinStrategies

export type ParseErrorHandlingOption =
| ParseErrorHandler
| ParseErrorHandlingStrategy

/**
* Resolve error handler function.
*
* @param option Either an error handler to return back or the name of the
* built-in handling strategy.
* @returns Resolved error handler.
* @throws {Error} If called with unknown built-in handling strategy name.
*/
export function resolveParseErrorHandler(option: ParseErrorHandlingOption) {
if (typeof option === 'function') return option

if (Object.hasOwn(builtinStrategies, option)) return builtinStrategies[option]

throw new Error(`Cannot resolve built-in strategy with name "${option}"`)
}

interface ParseErrorContext {
/** ID of the module that is being parsed. */
get moduleId(): string

/** ID of the message that cannot be parsed. */
get messageId(): string

/** Message that cannot be parsed. */
get message(): string

/** Error that occurred during the parsing. */
get error(): unknown

/** Parser options that were used to parse the message. */
get parserOptions(): ParserOptions | undefined

/**
* Call one of the built-in error handling strategies.
*
* @param name Name of the error handling strategy.
* @returns Result for the strategy.
*/
useBuiltinStrategy(name: ParseErrorHandlingStrategy): ParseErrorHandlerResult
}

/**
* Creates a new context.
*
* @param info Information required to create a context.
* @returns Newly created context object.
*/
export function createParseErrorContext(
info: Pick<
ParseErrorContext,
'error' | 'moduleId' | 'message' | 'messageId' | 'parserOptions'
>,
) {
const ctx = {
...info,
useBuiltinStrategy(name: ParseErrorHandlingStrategy) {
return resolveParseErrorHandler(name)(ctx)
},
}

return Object.freeze(ctx) satisfies ParseErrorContext
}
14 changes: 12 additions & 2 deletions src/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export { createOptionsResolver, AnyMessage } from './options.ts'
export type { MessagesParserOptionsValue } from './options.ts'
export {
createOptionsResolver,
AnyMessage,
type MessagesParserOptionsValue,
type ParserOptionsResolver,
} from './options.ts'
export { defaultOptionsResolver } from './default-options-resolver.ts'
export {
createParseErrorContext,
resolveParseErrorHandler,
type ParseErrorHandler,
type ParseErrorHandlingOption,
} from './error-handling.ts'
5 changes: 4 additions & 1 deletion src/platform-bindings/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { UnpluginInstance, RollupPlugin } from 'unplugin'
import { plugin as basePlugin, type Options } from '../plugin/index.ts'

export * from '../secondary-exports.ts'

export type PluginOptions = Options<RollupPlugin>

export const icuMessages = (
basePlugin as UnpluginInstance<Options<RollupPlugin>, false>
basePlugin as UnpluginInstance<PluginOptions, false>
).rollup
5 changes: 4 additions & 1 deletion src/platform-bindings/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { UnpluginInstance, VitePlugin } from 'unplugin'
import { plugin as basePlugin, type Options } from '../plugin/index.ts'

export * from '../secondary-exports.ts'

export type PluginOptions = Options<VitePlugin>

export const icuMessages = (
basePlugin as UnpluginInstance<Options<VitePlugin>, false>
basePlugin as UnpluginInstance<PluginOptions, false>
).vite
5 changes: 4 additions & 1 deletion src/platform-bindings/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { plugin as basePlugin } from '../plugin/index.ts'
import { plugin as basePlugin, type Options } from '../plugin/index.ts'

export * from '../secondary-exports.ts'

export type PluginOptions = Options<any>

export const icuMessages = basePlugin.webpack
Loading