babel-plugin-formatjs 11.0.7 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -3,7 +3,6 @@ import babelPluginSyntaxJsxNs from '@babel/plugin-syntax-jsx';
3
3
  import { visitor as CallExpression } from './visitors/call-expression.js';
4
4
  import { visitor as JSXOpeningElement } from './visitors/jsx-opening-element.js';
5
5
  const babelPluginSyntaxJsx = babelPluginSyntaxJsxNs.default || babelPluginSyntaxJsxNs;
6
- console.log(babelPluginSyntaxJsxNs);
7
6
  export const DEFAULT_ID_INTERPOLATION_PATTERN = '[sha512:contenthash:base64:6]';
8
7
  const plugin =
9
8
  // @ts-expect-error PluginPass doesn't allow custom state but it actually does
@@ -65,6 +64,8 @@ declare((api, options) => {
65
64
  },
66
65
  JSXOpeningElement,
67
66
  CallExpression,
67
+ // GH #4471: Handle optional chaining calls (e.g., intl.formatMessage?.())
68
+ OptionalCallExpression: CallExpression,
68
69
  },
69
70
  };
70
71
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "babel-plugin-formatjs",
3
3
  "description": "Extracts string messages for translation from modules that use formatjs.",
4
- "version": "11.0.7",
4
+ "version": "11.1.0",
5
5
  "license": "MIT",
6
6
  "author": "Long Ho <holevietlong@gmail.com>",
7
7
  "type": "module",
@@ -16,8 +16,8 @@
16
16
  "@types/babel__helper-plugin-utils": "^7.10.3",
17
17
  "@types/babel__traverse": "^7.20.6",
18
18
  "tslib": "^2.8.0",
19
- "@formatjs/icu-messageformat-parser": "3.2.0",
20
- "@formatjs/ts-transformer": "4.0.7"
19
+ "@formatjs/ts-transformer": "4.1.0",
20
+ "@formatjs/icu-messageformat-parser": "3.2.1"
21
21
  },
22
22
  "bugs": "https://github.com/formatjs/formatjs/issues",
23
23
  "gitHead": "8b0baec8eda5002715cf893274fe59782fc2d371",
package/types.d.ts CHANGED
@@ -27,4 +27,5 @@ export interface Options {
27
27
  extractSourceLocation?: boolean;
28
28
  ast?: boolean;
29
29
  preserveWhitespace?: boolean;
30
+ flatten?: boolean;
30
31
  }
package/utils.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare function createMessageDescriptor(propPaths: [
6
6
  NodePath<t.JSXIdentifier> | NodePath<t.Identifier>,
7
7
  NodePath<t.StringLiteral> | NodePath<t.JSXExpressionContainer>
8
8
  ][]): MessageDescriptorPath;
9
- export declare function evaluateMessageDescriptor(descriptorPath: MessageDescriptorPath, isJSXSource: boolean | undefined, filename: string | undefined, idInterpolationPattern?: string, overrideIdFn?: Options['overrideIdFn'], preserveWhitespace?: Options['preserveWhitespace']): MessageDescriptor;
9
+ export declare function evaluateMessageDescriptor(descriptorPath: MessageDescriptorPath, isJSXSource: boolean | undefined, filename: string | undefined, idInterpolationPattern?: string, overrideIdFn?: Options['overrideIdFn'], preserveWhitespace?: Options['preserveWhitespace'], flatten?: Options['flatten']): MessageDescriptor;
10
10
  /**
11
11
  * Tag a node as extracted
12
12
  * Store this in the node itself so that multiple passes work. Specifically
package/utils.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { parse } from '@formatjs/icu-messageformat-parser';
2
+ import { hoistSelectors } from '@formatjs/icu-messageformat-parser/manipulator.js';
3
+ import { printAST } from '@formatjs/icu-messageformat-parser/printer.js';
2
4
  import { interpolateName } from '@formatjs/ts-transformer';
3
5
  const DESCRIPTOR_PROPS = new Set([
4
6
  'id',
@@ -46,12 +48,23 @@ export function createMessageDescriptor(propPaths) {
46
48
  description: undefined,
47
49
  });
48
50
  }
49
- export function evaluateMessageDescriptor(descriptorPath, isJSXSource = false, filename, idInterpolationPattern, overrideIdFn, preserveWhitespace) {
51
+ export function evaluateMessageDescriptor(descriptorPath, isJSXSource = false, filename, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten) {
50
52
  let id = getMessageDescriptorValue(descriptorPath.id);
51
- const defaultMessage = getICUMessageValue(descriptorPath.defaultMessage, {
53
+ let defaultMessage = getICUMessageValue(descriptorPath.defaultMessage, {
52
54
  isJSXSource,
53
55
  }, preserveWhitespace);
54
56
  const description = getMessageDescriptorValue(descriptorPath.description);
57
+ // GH #3537: Apply flatten transformation before calling overrideIdFn
58
+ // so that the ID generation sees the same message format as the final output
59
+ if (flatten && defaultMessage) {
60
+ try {
61
+ defaultMessage = printAST(hoistSelectors(parse(defaultMessage)));
62
+ }
63
+ catch (e) {
64
+ // If flatten fails, continue with original message
65
+ // The error will be caught again during validation
66
+ }
67
+ }
55
68
  if (overrideIdFn) {
56
69
  id = overrideIdFn(id, defaultMessage, description, filename);
57
70
  }
@@ -10,7 +10,9 @@ function isFormatMessageCall(callee, functionNames) {
10
10
  if (functionNames.find(name => callee.isIdentifier({ name }))) {
11
11
  return true;
12
12
  }
13
- if (callee.isMemberExpression()) {
13
+ // GH #4471: Handle both MemberExpression and OptionalMemberExpression
14
+ // (e.g., intl.formatMessage() and intl.formatMessage?.())
15
+ if (callee.isMemberExpression() || callee.isOptionalMemberExpression()) {
14
16
  const property = callee.get('property');
15
17
  return !!functionNames.find(name => property.isIdentifier({ name }));
16
18
  }
@@ -26,7 +28,7 @@ function getMessagesObjectFromExpression(nodePath) {
26
28
  return currentPath;
27
29
  }
28
30
  export const visitor = function (path, { opts, file: { opts: { filename }, }, }) {
29
- const { overrideIdFn, idInterpolationPattern, removeDefaultMessage, ast, preserveWhitespace, } = opts;
31
+ const { overrideIdFn, idInterpolationPattern, removeDefaultMessage, ast, preserveWhitespace, flatten, } = opts;
30
32
  if (wasExtracted(path)) {
31
33
  return;
32
34
  }
@@ -46,7 +48,7 @@ export const visitor = function (path, { opts, file: { opts: { filename }, }, })
46
48
  return;
47
49
  }
48
50
  // Evaluate the Message Descriptor values, then store it.
49
- const descriptor = evaluateMessageDescriptor(descriptorPath, false, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace);
51
+ const descriptor = evaluateMessageDescriptor(descriptorPath, false, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten);
50
52
  storeMessage(descriptor, messageDescriptor, opts, filename || undefined, messages);
51
53
  const firstProp = properties[0];
52
54
  const defaultMessageProp = properties.find(prop => {
@@ -2,7 +2,7 @@ import * as t from '@babel/types';
2
2
  import { parse } from '@formatjs/icu-messageformat-parser';
3
3
  import { createMessageDescriptor, evaluateMessageDescriptor, getMessageDescriptorKey, storeMessage, tagAsExtracted, wasExtracted, } from '../utils.js';
4
4
  export const visitor = function (path, { opts, file: { opts: { filename }, }, }) {
5
- const { removeDefaultMessage, idInterpolationPattern, overrideIdFn, ast, preserveWhitespace, } = opts;
5
+ const { removeDefaultMessage, idInterpolationPattern, overrideIdFn, ast, preserveWhitespace, flatten, } = opts;
6
6
  const { componentNames, messages } = this;
7
7
  if (wasExtracted(path)) {
8
8
  return;
@@ -29,7 +29,7 @@ export const visitor = function (path, { opts, file: { opts: { filename }, }, })
29
29
  }
30
30
  // Evaluate the Message Descriptor values in a JSX
31
31
  // context, then store it.
32
- const descriptor = evaluateMessageDescriptor(descriptorPath, true, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace);
32
+ const descriptor = evaluateMessageDescriptor(descriptorPath, true, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten);
33
33
  storeMessage(descriptor, path, opts, filename || undefined, messages);
34
34
  let idAttr;
35
35
  let descriptionAttr;