babel-plugin-formatjs 11.1.0 → 11.2.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.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { PluginObj, PluginPass } from '@babel/core';
2
- import { ExtractedMessageDescriptor, Options } from './types.js';
1
+ import { type PluginObj, type PluginPass } from "@babel/core";
2
+ import { type ExtractedMessageDescriptor, type Options } from "./types.js";
3
3
  export type ExtractionResult<M = Record<string, string>> = {
4
- messages: ExtractedMessageDescriptor[];
5
- meta: M;
4
+ messages: ExtractedMessageDescriptor[];
5
+ meta: M;
6
6
  };
7
7
  export declare const DEFAULT_ID_INTERPOLATION_PATTERN = "[sha512:contenthash:base64:6]";
8
8
  declare const plugin: (api: object, options: Options | null | undefined, dirname: string) => PluginObj<PluginPass>;
package/index.js CHANGED
@@ -1,72 +1,67 @@
1
- import { declare } from '@babel/helper-plugin-utils';
2
- import babelPluginSyntaxJsxNs from '@babel/plugin-syntax-jsx';
3
- import { visitor as CallExpression } from './visitors/call-expression.js';
4
- import { visitor as JSXOpeningElement } from './visitors/jsx-opening-element.js';
1
+ import "@babel/core";
2
+ import { declare } from "@babel/helper-plugin-utils";
3
+ import babelPluginSyntaxJsxNs from "@babel/plugin-syntax-jsx";
4
+ import "./types.js";
5
+ import { visitor as CallExpression } from "./visitors/call-expression.js";
6
+ import { visitor as JSXOpeningElement } from "./visitors/jsx-opening-element.js";
5
7
  const babelPluginSyntaxJsx = babelPluginSyntaxJsxNs.default || babelPluginSyntaxJsxNs;
6
- export const DEFAULT_ID_INTERPOLATION_PATTERN = '[sha512:contenthash:base64:6]';
7
- const plugin =
8
- // @ts-expect-error PluginPass doesn't allow custom state but it actually does
9
- declare((api, options) => {
10
- api.assertVersion(7);
11
- if (!options.idInterpolationPattern) {
12
- options.idInterpolationPattern = DEFAULT_ID_INTERPOLATION_PATTERN;
13
- }
14
- const { pragma } = options;
15
- const componentNames = new Set(options.additionalComponentNames);
16
- componentNames.add('FormattedMessage');
17
- const functionNames = new Set(options.additionalFunctionNames);
18
- functionNames.add('formatMessage');
19
- // Short hand
20
- functionNames.add('$t');
21
- // Vue
22
- functionNames.add('$formatMessage');
23
- return {
24
- inherits: babelPluginSyntaxJsx,
25
- pre() {
26
- this.componentNames = Array.from(componentNames);
27
- this.functionNames = Array.from(functionNames);
28
- },
29
- visitor: {
30
- Program: {
31
- enter(path) {
32
- this.messages = [];
33
- this.meta = {};
34
- if (!pragma) {
35
- return;
36
- }
37
- for (const { leadingComments } of path.node.body) {
38
- if (!leadingComments) {
39
- continue;
40
- }
41
- const pragmaLineNode = leadingComments.find(c => c.value.includes(pragma));
42
- if (!pragmaLineNode) {
43
- continue;
44
- }
45
- pragmaLineNode.value
46
- .split(pragma)[1]
47
- .trim()
48
- .split(/\s+/g)
49
- .forEach(kv => {
50
- const [k, v] = kv.split(':');
51
- this.meta[k] = v;
52
- });
53
- }
54
- },
55
- exit(_, { opts: _opts, file: { opts: { filename }, }, }) {
56
- const opts = _opts;
57
- if (typeof opts?.onMetaExtracted === 'function') {
58
- opts.onMetaExtracted(filename || '', this.meta);
59
- }
60
- if (typeof opts?.onMsgExtracted === 'function') {
61
- opts.onMsgExtracted(filename || '', this.messages);
62
- }
63
- },
64
- },
65
- JSXOpeningElement,
66
- CallExpression,
67
- // GH #4471: Handle optional chaining calls (e.g., intl.formatMessage?.())
68
- OptionalCallExpression: CallExpression,
69
- },
70
- };
8
+ export const DEFAULT_ID_INTERPOLATION_PATTERN = "[sha512:contenthash:base64:6]";
9
+ const plugin = declare((api, options) => {
10
+ api.assertVersion(7);
11
+ if (!options.idInterpolationPattern) {
12
+ options.idInterpolationPattern = DEFAULT_ID_INTERPOLATION_PATTERN;
13
+ }
14
+ const { pragma } = options;
15
+ const componentNames = new Set(options.additionalComponentNames);
16
+ componentNames.add("FormattedMessage");
17
+ const functionNames = new Set(options.additionalFunctionNames);
18
+ functionNames.add("formatMessage");
19
+ // Short hand
20
+ functionNames.add("$t");
21
+ // Vue
22
+ functionNames.add("$formatMessage");
23
+ return {
24
+ inherits: babelPluginSyntaxJsx,
25
+ pre() {
26
+ this.componentNames = Array.from(componentNames);
27
+ this.functionNames = Array.from(functionNames);
28
+ },
29
+ visitor: {
30
+ Program: {
31
+ enter(path) {
32
+ this.messages = [];
33
+ this.meta = {};
34
+ if (!pragma) {
35
+ return;
36
+ }
37
+ for (const { leadingComments } of path.node.body) {
38
+ if (!leadingComments) {
39
+ continue;
40
+ }
41
+ const pragmaLineNode = leadingComments.find((c) => c.value.includes(pragma));
42
+ if (!pragmaLineNode) {
43
+ continue;
44
+ }
45
+ pragmaLineNode.value.split(pragma)[1].trim().split(/\s+/g).forEach((kv) => {
46
+ const [k, v] = kv.split(":");
47
+ this.meta[k] = v;
48
+ });
49
+ }
50
+ },
51
+ exit(_, { opts: _opts, file: { opts: { filename } } }) {
52
+ const opts = _opts;
53
+ if (typeof opts?.onMetaExtracted === "function") {
54
+ opts.onMetaExtracted(filename || "", this.meta);
55
+ }
56
+ if (typeof opts?.onMsgExtracted === "function") {
57
+ opts.onMsgExtracted(filename || "", this.messages);
58
+ }
59
+ }
60
+ },
61
+ JSXOpeningElement,
62
+ CallExpression,
63
+ OptionalCallExpression: CallExpression
64
+ }
65
+ };
71
66
  });
72
67
  export default plugin;
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "babel-plugin-formatjs",
3
3
  "description": "Extracts string messages for translation from modules that use formatjs.",
4
- "version": "11.1.0",
4
+ "version": "11.2.0",
5
5
  "license": "MIT",
6
6
  "author": "Long Ho <holevietlong@gmail.com>",
7
7
  "type": "module",
8
8
  "types": "index.d.ts",
9
9
  "dependencies": {
10
- "@babel/core": "^7.26.10",
11
- "@babel/helper-plugin-utils": "^7.26.5",
12
- "@babel/plugin-syntax-jsx": "^7.25.9",
13
- "@babel/traverse": "^7.26.10",
14
- "@babel/types": "^7.26.10",
10
+ "@babel/core": "^7.28.5",
11
+ "@babel/helper-plugin-utils": "^7.27.1",
12
+ "@babel/plugin-syntax-jsx": "^7.27.1",
13
+ "@babel/traverse": "^7.28.5",
14
+ "@babel/types": "^7.28.5",
15
15
  "@types/babel__core": "^7.20.5",
16
16
  "@types/babel__helper-plugin-utils": "^7.10.3",
17
- "@types/babel__traverse": "^7.20.6",
18
- "tslib": "^2.8.0",
19
- "@formatjs/ts-transformer": "4.1.0",
20
- "@formatjs/icu-messageformat-parser": "3.2.1"
17
+ "@types/babel__traverse": "^7.28.0",
18
+ "tslib": "^2.8.1",
19
+ "@formatjs/icu-messageformat-parser": "3.4.0",
20
+ "@formatjs/ts-transformer": "4.3.0"
21
21
  },
22
22
  "bugs": "https://github.com/formatjs/formatjs/issues",
23
23
  "gitHead": "8b0baec8eda5002715cf893274fe59782fc2d371",
package/types.d.ts CHANGED
@@ -1,31 +1,31 @@
1
- import { NodePath } from '@babel/core';
2
- import { JSXExpressionContainer, SourceLocation, StringLiteral } from '@babel/types';
1
+ import { type NodePath } from "@babel/core";
2
+ import { type JSXExpressionContainer, type SourceLocation, type StringLiteral } from "@babel/types";
3
3
  export interface MessageDescriptor {
4
- id: string;
5
- defaultMessage?: string;
6
- description?: string;
4
+ id: string;
5
+ defaultMessage?: string;
6
+ description?: string;
7
7
  }
8
8
  export interface State {
9
- messages: ExtractedMessageDescriptor[];
10
- meta: Record<string, string>;
11
- componentNames: string[];
12
- functionNames: string[];
9
+ messages: ExtractedMessageDescriptor[];
10
+ meta: Record<string, string>;
11
+ componentNames: string[];
12
+ functionNames: string[];
13
13
  }
14
14
  export type ExtractedMessageDescriptor = MessageDescriptor & Partial<SourceLocation> & {
15
- file?: string;
15
+ file?: string;
16
16
  };
17
17
  export type MessageDescriptorPath = Record<keyof MessageDescriptor, NodePath<StringLiteral> | NodePath<JSXExpressionContainer> | undefined>;
18
18
  export interface Options {
19
- overrideIdFn?: (id?: string, defaultMessage?: string, description?: string, filePath?: string) => string;
20
- onMsgExtracted?: (filePath: string, msgs: MessageDescriptor[]) => void;
21
- onMetaExtracted?: (filePath: string, meta: Record<string, string>) => void;
22
- idInterpolationPattern?: string;
23
- removeDefaultMessage?: boolean;
24
- additionalComponentNames?: string[];
25
- additionalFunctionNames?: string[];
26
- pragma?: string;
27
- extractSourceLocation?: boolean;
28
- ast?: boolean;
29
- preserveWhitespace?: boolean;
30
- flatten?: boolean;
19
+ overrideIdFn?: (id?: string, defaultMessage?: string, description?: string, filePath?: string) => string;
20
+ onMsgExtracted?: (filePath: string, msgs: MessageDescriptor[]) => void;
21
+ onMetaExtracted?: (filePath: string, meta: Record<string, string>) => void;
22
+ idInterpolationPattern?: string;
23
+ removeDefaultMessage?: boolean;
24
+ additionalComponentNames?: string[];
25
+ additionalFunctionNames?: string[];
26
+ pragma?: string;
27
+ extractSourceLocation?: boolean;
28
+ ast?: boolean;
29
+ preserveWhitespace?: boolean;
30
+ flatten?: boolean;
31
31
  }
package/types.js CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ import "@babel/core";
2
+ import "@babel/types";
package/utils.d.ts CHANGED
@@ -1,33 +1,30 @@
1
- import * as t from '@babel/types';
2
- import { NodePath } from '@babel/core';
3
- import { ExtractedMessageDescriptor, MessageDescriptor, MessageDescriptorPath, Options } from './types.js';
1
+ import type * as t from "@babel/types";
2
+ import { type NodePath } from "@babel/core";
3
+ import { type ExtractedMessageDescriptor, type MessageDescriptor, type MessageDescriptorPath, type Options } from "./types.js";
4
4
  export declare function getMessageDescriptorKey(path: NodePath<any>): string;
5
- export declare function createMessageDescriptor(propPaths: [
6
- NodePath<t.JSXIdentifier> | NodePath<t.Identifier>,
7
- NodePath<t.StringLiteral> | NodePath<t.JSXExpressionContainer>
8
- ][]): MessageDescriptorPath;
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;
5
+ export declare function createMessageDescriptor(propPaths: [NodePath<t.JSXIdentifier> | NodePath<t.Identifier>, NodePath<t.StringLiteral> | NodePath<t.JSXExpressionContainer>][]): MessageDescriptorPath;
6
+ 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
7
  /**
11
- * Tag a node as extracted
12
- * Store this in the node itself so that multiple passes work. Specifically
13
- * if we remove `description` in the 1st pass, 2nd pass will fail since
14
- * it expect `description` to be there.
15
- * HACK: We store this in the node instance since this persists across
16
- * multiple plugin runs
17
- * @param path
18
- */
8
+ * Tag a node as extracted
9
+ * Store this in the node itself so that multiple passes work. Specifically
10
+ * if we remove `description` in the 1st pass, 2nd pass will fail since
11
+ * it expect `description` to be there.
12
+ * HACK: We store this in the node instance since this persists across
13
+ * multiple plugin runs
14
+ * @param path
15
+ */
19
16
  export declare function tagAsExtracted(path: NodePath<any>): void;
20
17
  /**
21
- * Check if a node was extracted
22
- * @param path
23
- */
18
+ * Check if a node was extracted
19
+ * @param path
20
+ */
24
21
  export declare function wasExtracted(path: NodePath<any>): boolean;
25
22
  /**
26
- * Store a message in our global messages
27
- * @param messageDescriptor
28
- * @param path
29
- * @param opts
30
- * @param filename
31
- * @param messages
32
- */
23
+ * Store a message in our global messages
24
+ * @param messageDescriptor
25
+ * @param path
26
+ * @param opts
27
+ * @param filename
28
+ * @param messages
29
+ */
33
30
  export declare function storeMessage({ id, description, defaultMessage }: MessageDescriptor, path: NodePath<any>, { extractSourceLocation }: Options, filename: string | undefined, messages: ExtractedMessageDescriptor[]): void;
package/utils.js CHANGED
@@ -1,156 +1,141 @@
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';
4
- import { interpolateName } from '@formatjs/ts-transformer';
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";
4
+ import { interpolateName } from "@formatjs/ts-transformer";
5
+ import "@babel/core";
6
+ import "./types.js";
5
7
  const DESCRIPTOR_PROPS = new Set([
6
- 'id',
7
- 'description',
8
- 'defaultMessage',
8
+ "id",
9
+ "description",
10
+ "defaultMessage"
9
11
  ]);
10
12
  function evaluatePath(path) {
11
- const evaluated = path.evaluate();
12
- if (evaluated.confident) {
13
- return evaluated.value;
14
- }
15
- throw path.buildCodeFrameError('[React Intl] Messages must be statically evaluate-able for extraction.');
13
+ const evaluated = path.evaluate();
14
+ if (evaluated.confident) {
15
+ return evaluated.value;
16
+ }
17
+ throw path.buildCodeFrameError("[React Intl] Messages must be statically evaluate-able for extraction.");
16
18
  }
17
19
  export function getMessageDescriptorKey(path) {
18
- if (path.isIdentifier() || path.isJSXIdentifier()) {
19
- return path.node.name;
20
- }
21
- return evaluatePath(path);
20
+ if (path.isIdentifier() || path.isJSXIdentifier()) {
21
+ return path.node.name;
22
+ }
23
+ return evaluatePath(path);
22
24
  }
23
25
  function getMessageDescriptorValue(path, isMessageNode) {
24
- if (!path) {
25
- return '';
26
- }
27
- if (path.isJSXExpressionContainer()) {
28
- // If this is already compiled, no need to recompiled it
29
- if (isMessageNode && path.get('expression').isArrayExpression()) {
30
- return '';
31
- }
32
- path = path.get('expression');
33
- }
34
- // Always trim the Message Descriptor values.
35
- const descriptorValue = evaluatePath(path);
36
- return descriptorValue;
26
+ if (!path) {
27
+ return "";
28
+ }
29
+ if (path.isJSXExpressionContainer()) {
30
+ // If this is already compiled, no need to recompiled it
31
+ if (isMessageNode && path.get("expression").isArrayExpression()) {
32
+ return "";
33
+ }
34
+ path = path.get("expression");
35
+ }
36
+ // Always trim the Message Descriptor values.
37
+ const descriptorValue = evaluatePath(path);
38
+ return descriptorValue;
37
39
  }
38
40
  export function createMessageDescriptor(propPaths) {
39
- return propPaths.reduce((hash, [keyPath, valuePath]) => {
40
- const key = getMessageDescriptorKey(keyPath);
41
- if (DESCRIPTOR_PROPS.has(key)) {
42
- hash[key] = valuePath;
43
- }
44
- return hash;
45
- }, {
46
- id: undefined,
47
- defaultMessage: undefined,
48
- description: undefined,
49
- });
41
+ return propPaths.reduce((hash, [keyPath, valuePath]) => {
42
+ const key = getMessageDescriptorKey(keyPath);
43
+ if (DESCRIPTOR_PROPS.has(key)) {
44
+ hash[key] = valuePath;
45
+ }
46
+ return hash;
47
+ }, {
48
+ id: undefined,
49
+ defaultMessage: undefined,
50
+ description: undefined
51
+ });
50
52
  }
51
53
  export function evaluateMessageDescriptor(descriptorPath, isJSXSource = false, filename, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten) {
52
- let id = getMessageDescriptorValue(descriptorPath.id);
53
- let defaultMessage = getICUMessageValue(descriptorPath.defaultMessage, {
54
- isJSXSource,
55
- }, preserveWhitespace);
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
- }
68
- if (overrideIdFn) {
69
- id = overrideIdFn(id, defaultMessage, description, filename);
70
- }
71
- else if (!id && idInterpolationPattern && defaultMessage) {
72
- id = interpolateName({ resourcePath: filename }, idInterpolationPattern, {
73
- content: description
74
- ? `${defaultMessage}#${description}`
75
- : defaultMessage,
76
- });
77
- }
78
- const descriptor = {
79
- id,
80
- };
81
- if (description) {
82
- descriptor.description = description;
83
- }
84
- if (defaultMessage) {
85
- descriptor.defaultMessage = defaultMessage;
86
- }
87
- return descriptor;
54
+ let id = getMessageDescriptorValue(descriptorPath.id);
55
+ let defaultMessage = getICUMessageValue(descriptorPath.defaultMessage, { isJSXSource }, preserveWhitespace);
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
+ } catch {}
63
+ }
64
+ if (overrideIdFn) {
65
+ id = overrideIdFn(id, defaultMessage, description, filename);
66
+ } else if (!id && idInterpolationPattern && defaultMessage) {
67
+ id = interpolateName({ resourcePath: filename }, idInterpolationPattern, { content: description ? `${defaultMessage}#${description}` : defaultMessage });
68
+ }
69
+ const descriptor = { id };
70
+ if (description) {
71
+ descriptor.description = description;
72
+ }
73
+ if (defaultMessage) {
74
+ descriptor.defaultMessage = defaultMessage;
75
+ }
76
+ return descriptor;
88
77
  }
89
78
  function getICUMessageValue(messagePath, { isJSXSource = false } = {}, preserveWhitespace) {
90
- if (!messagePath) {
91
- return '';
92
- }
93
- let message = getMessageDescriptorValue(messagePath, true);
94
- if (!preserveWhitespace) {
95
- message = message.trim().replace(/\s+/gm, ' ');
96
- }
97
- try {
98
- parse(message);
99
- }
100
- catch (parseError) {
101
- if (isJSXSource &&
102
- messagePath.isLiteral() &&
103
- message.indexOf('\\\\') >= 0) {
104
- throw messagePath.buildCodeFrameError('[React Intl] Message failed to parse. ' +
105
- 'It looks like `\\`s were used for escaping, ' +
106
- "this won't work with JSX string literals. " +
107
- 'Wrap with `{}`. ' +
108
- 'See: http://facebook.github.io/react/docs/jsx-gotchas.html');
109
- }
110
- throw messagePath.buildCodeFrameError('[React Intl] Message failed to parse. ' +
111
- 'See: https://formatjs.github.io/docs/core-concepts/icu-syntax' +
112
- `\n${parseError}`);
113
- }
114
- return message;
79
+ if (!messagePath) {
80
+ return "";
81
+ }
82
+ let message = getMessageDescriptorValue(messagePath, true);
83
+ if (!preserveWhitespace) {
84
+ message = message.trim().replace(/\s+/gm, " ");
85
+ }
86
+ try {
87
+ parse(message);
88
+ } catch (parseError) {
89
+ if (isJSXSource && messagePath.isLiteral() && message.indexOf("\\\\") >= 0) {
90
+ throw messagePath.buildCodeFrameError("[React Intl] Message failed to parse. " + "It looks like `\\`s were used for escaping, " + "this won't work with JSX string literals. " + "Wrap with `{}`. " + "See: http://facebook.github.io/react/docs/jsx-gotchas.html");
91
+ }
92
+ throw messagePath.buildCodeFrameError("[React Intl] Message failed to parse. " + "See: https://formatjs.github.io/docs/core-concepts/icu-syntax" + `\n${parseError}`);
93
+ }
94
+ return message;
115
95
  }
116
- const EXTRACTED = Symbol('FormatJSExtracted');
96
+ const EXTRACTED = Symbol("FormatJSExtracted");
117
97
  /**
118
- * Tag a node as extracted
119
- * Store this in the node itself so that multiple passes work. Specifically
120
- * if we remove `description` in the 1st pass, 2nd pass will fail since
121
- * it expect `description` to be there.
122
- * HACK: We store this in the node instance since this persists across
123
- * multiple plugin runs
124
- * @param path
125
- */
98
+ * Tag a node as extracted
99
+ * Store this in the node itself so that multiple passes work. Specifically
100
+ * if we remove `description` in the 1st pass, 2nd pass will fail since
101
+ * it expect `description` to be there.
102
+ * HACK: We store this in the node instance since this persists across
103
+ * multiple plugin runs
104
+ * @param path
105
+ */
126
106
  export function tagAsExtracted(path) {
127
- path.node[EXTRACTED] = true;
107
+ path.node[EXTRACTED] = true;
128
108
  }
129
109
  /**
130
- * Check if a node was extracted
131
- * @param path
132
- */
110
+ * Check if a node was extracted
111
+ * @param path
112
+ */
133
113
  export function wasExtracted(path) {
134
- return !!path.node[EXTRACTED];
114
+ return !!path.node[EXTRACTED];
135
115
  }
136
116
  /**
137
- * Store a message in our global messages
138
- * @param messageDescriptor
139
- * @param path
140
- * @param opts
141
- * @param filename
142
- * @param messages
143
- */
117
+ * Store a message in our global messages
118
+ * @param messageDescriptor
119
+ * @param path
120
+ * @param opts
121
+ * @param filename
122
+ * @param messages
123
+ */
144
124
  export function storeMessage({ id, description, defaultMessage }, path, { extractSourceLocation }, filename, messages) {
145
- if (!id && !defaultMessage) {
146
- throw path.buildCodeFrameError('[React Intl] Message Descriptors require an `id` or `defaultMessage`.');
147
- }
148
- let loc = {};
149
- if (extractSourceLocation) {
150
- loc = {
151
- file: filename,
152
- ...path.node.loc,
153
- };
154
- }
155
- messages.push({ id, description, defaultMessage, ...loc });
125
+ if (!id && !defaultMessage) {
126
+ throw path.buildCodeFrameError("[React Intl] Message Descriptors require an `id` or `defaultMessage`.");
127
+ }
128
+ let loc = {};
129
+ if (extractSourceLocation) {
130
+ loc = {
131
+ file: filename,
132
+ ...path.node.loc
133
+ };
134
+ }
135
+ messages.push({
136
+ id,
137
+ description,
138
+ defaultMessage,
139
+ ...loc
140
+ });
156
141
  }
@@ -1,5 +1,5 @@
1
- import { PluginPass } from '@babel/core';
2
- import * as t from '@babel/types';
3
- import { State } from '../types.js';
4
- import { VisitNodeFunction } from '@babel/traverse';
1
+ import { type PluginPass } from "@babel/core";
2
+ import * as t from "@babel/types";
3
+ import { type State } from "../types.js";
4
+ import { type VisitNodeFunction } from "@babel/traverse";
5
5
  export declare const visitor: VisitNodeFunction<PluginPass & State, t.CallExpression>;
@@ -1,121 +1,110 @@
1
- import * as t from '@babel/types';
2
- import { createMessageDescriptor, evaluateMessageDescriptor, wasExtracted, storeMessage, tagAsExtracted, } from '../utils.js';
3
- import { parse } from '@formatjs/icu-messageformat-parser';
1
+ import "@babel/core";
2
+ import * as t from "@babel/types";
3
+ import "../types.js";
4
+ import "@babel/traverse";
5
+ import { createMessageDescriptor, evaluateMessageDescriptor, wasExtracted, storeMessage, tagAsExtracted } from "../utils.js";
6
+ import { parse } from "@formatjs/icu-messageformat-parser";
4
7
  function assertObjectExpression(path, callee) {
5
- if (!path || !path.isObjectExpression()) {
6
- throw path.buildCodeFrameError(`[React Intl] \`${callee.get('property').node.name}()\` must be called with an object expression with values that are React Intl Message Descriptors, also defined as object expressions.`);
7
- }
8
+ if (!path || !path.isObjectExpression()) {
9
+ throw path.buildCodeFrameError(`[React Intl] \`${callee.get("property").node.name}()\` must be called with an object expression with values that are React Intl Message Descriptors, also defined as object expressions.`);
10
+ }
8
11
  }
9
12
  function isFormatMessageCall(callee, functionNames) {
10
- if (functionNames.find(name => callee.isIdentifier({ name }))) {
11
- return true;
12
- }
13
- // GH #4471: Handle both MemberExpression and OptionalMemberExpression
14
- // (e.g., intl.formatMessage() and intl.formatMessage?.())
15
- if (callee.isMemberExpression() || callee.isOptionalMemberExpression()) {
16
- const property = callee.get('property');
17
- return !!functionNames.find(name => property.isIdentifier({ name }));
18
- }
19
- return false;
13
+ if (functionNames.find((name) => callee.isIdentifier({ name }))) {
14
+ return true;
15
+ }
16
+ // GH #4471: Handle both MemberExpression and OptionalMemberExpression
17
+ // (e.g., intl.formatMessage() and intl.formatMessage?.())
18
+ if (callee.isMemberExpression() || callee.isOptionalMemberExpression()) {
19
+ const property = callee.get("property");
20
+ return !!functionNames.find((name) => property.isIdentifier({ name }));
21
+ }
22
+ return false;
20
23
  }
21
24
  function getMessagesObjectFromExpression(nodePath) {
22
- let currentPath = nodePath;
23
- while (t.isTSAsExpression(currentPath.node) ||
24
- t.isTSTypeAssertion(currentPath.node) ||
25
- t.isTypeCastExpression(currentPath.node)) {
26
- currentPath = currentPath.get('expression');
27
- }
28
- return currentPath;
25
+ let currentPath = nodePath;
26
+ while (t.isTSAsExpression(currentPath.node) || t.isTSTypeAssertion(currentPath.node) || t.isTypeCastExpression(currentPath.node)) {
27
+ currentPath = currentPath.get("expression");
28
+ }
29
+ return currentPath;
29
30
  }
30
- export const visitor = function (path, { opts, file: { opts: { filename }, }, }) {
31
- const { overrideIdFn, idInterpolationPattern, removeDefaultMessage, ast, preserveWhitespace, flatten, } = opts;
32
- if (wasExtracted(path)) {
33
- return;
34
- }
35
- const { messages, functionNames } = this;
36
- const callee = path.get('callee');
37
- const args = path.get('arguments');
38
- /**
39
- * Process MessageDescriptor
40
- * @param messageDescriptor Message Descriptor
41
- */
42
- function processMessageObject(messageDescriptor) {
43
- assertObjectExpression(messageDescriptor, callee);
44
- const properties = messageDescriptor.get('properties');
45
- const descriptorPath = createMessageDescriptor(properties.map(prop => [prop.get('key'), prop.get('value')]));
46
- // If the message is already compiled, don't re-compile it
47
- if (descriptorPath.defaultMessage?.isArrayExpression()) {
48
- return;
49
- }
50
- // Evaluate the Message Descriptor values, then store it.
51
- const descriptor = evaluateMessageDescriptor(descriptorPath, false, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten);
52
- storeMessage(descriptor, messageDescriptor, opts, filename || undefined, messages);
53
- const firstProp = properties[0];
54
- const defaultMessageProp = properties.find(prop => {
55
- const keyProp = prop.get('key');
56
- return (keyProp.isIdentifier({ name: 'defaultMessage' }) ||
57
- keyProp.isStringLiteral({ value: 'defaultMessage' }));
58
- });
59
- const idProp = properties.find(prop => {
60
- const keyProp = prop.get('key');
61
- return (keyProp.isIdentifier({ name: 'id' }) ||
62
- keyProp.isStringLiteral({ value: 'id' }));
63
- });
64
- // Insert ID potentially 1st before removing nodes
65
- if (idProp) {
66
- idProp.get('value').replaceWith(t.stringLiteral(descriptor.id));
67
- }
68
- else {
69
- firstProp.insertBefore(t.objectProperty(t.identifier('id'), t.stringLiteral(descriptor.id)));
70
- }
71
- // Remove description
72
- properties
73
- .find(prop => {
74
- const keyProp = prop.get('key');
75
- return (keyProp.isIdentifier({ name: 'description' }) ||
76
- keyProp.isStringLiteral({ value: 'description' }));
77
- })
78
- ?.remove();
79
- // Pre-parse or remove defaultMessage
80
- if (defaultMessageProp) {
81
- if (removeDefaultMessage) {
82
- defaultMessageProp?.remove();
83
- }
84
- else if (descriptor.defaultMessage) {
85
- const valueProp = defaultMessageProp.get('value');
86
- if (ast) {
87
- valueProp.replaceWithSourceString(JSON.stringify(parse(descriptor.defaultMessage)));
88
- }
89
- else {
90
- valueProp.replaceWith(t.stringLiteral(descriptor.defaultMessage));
91
- }
92
- }
93
- }
94
- tagAsExtracted(path);
95
- }
96
- // Check that this is `defineMessages` call
97
- if (callee.isIdentifier({ name: 'defineMessages' }) ||
98
- callee.isIdentifier({ name: 'defineMessage' })) {
99
- const firstArgument = args[0];
100
- const messagesObj = getMessagesObjectFromExpression(firstArgument);
101
- assertObjectExpression(messagesObj, callee);
102
- if (callee.isIdentifier({ name: 'defineMessage' })) {
103
- processMessageObject(messagesObj);
104
- }
105
- else {
106
- const properties = messagesObj.get('properties');
107
- if (Array.isArray(properties)) {
108
- properties
109
- .map(prop => prop.get('value'))
110
- .forEach(processMessageObject);
111
- }
112
- }
113
- }
114
- // Check that this is `intl.formatMessage` call
115
- if (isFormatMessageCall(callee, functionNames)) {
116
- const messageDescriptor = args[0];
117
- if (messageDescriptor && messageDescriptor.isObjectExpression()) {
118
- processMessageObject(messageDescriptor);
119
- }
120
- }
31
+ export const visitor = function(path, { opts, file: { opts: { filename } } }) {
32
+ const { overrideIdFn, idInterpolationPattern, removeDefaultMessage, ast, preserveWhitespace, flatten } = opts;
33
+ if (wasExtracted(path)) {
34
+ return;
35
+ }
36
+ const { messages, functionNames } = this;
37
+ const callee = path.get("callee");
38
+ const args = path.get("arguments");
39
+ /**
40
+ * Process MessageDescriptor
41
+ * @param messageDescriptor Message Descriptor
42
+ */
43
+ function processMessageObject(messageDescriptor) {
44
+ assertObjectExpression(messageDescriptor, callee);
45
+ const properties = messageDescriptor.get("properties");
46
+ const descriptorPath = createMessageDescriptor(properties.map((prop) => [prop.get("key"), prop.get("value")]));
47
+ // If the message is already compiled, don't re-compile it
48
+ if (descriptorPath.defaultMessage?.isArrayExpression()) {
49
+ return;
50
+ }
51
+ // Evaluate the Message Descriptor values, then store it.
52
+ const descriptor = evaluateMessageDescriptor(descriptorPath, false, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten);
53
+ storeMessage(descriptor, messageDescriptor, opts, filename || undefined, messages);
54
+ const firstProp = properties[0];
55
+ const defaultMessageProp = properties.find((prop) => {
56
+ const keyProp = prop.get("key");
57
+ return keyProp.isIdentifier({ name: "defaultMessage" }) || keyProp.isStringLiteral({ value: "defaultMessage" });
58
+ });
59
+ const idProp = properties.find((prop) => {
60
+ const keyProp = prop.get("key");
61
+ return keyProp.isIdentifier({ name: "id" }) || keyProp.isStringLiteral({ value: "id" });
62
+ });
63
+ // Insert ID potentially 1st before removing nodes
64
+ if (idProp) {
65
+ idProp.get("value").replaceWith(t.stringLiteral(descriptor.id));
66
+ } else {
67
+ firstProp.insertBefore(t.objectProperty(t.identifier("id"), t.stringLiteral(descriptor.id)));
68
+ }
69
+ // Remove description
70
+ properties.find((prop) => {
71
+ const keyProp = prop.get("key");
72
+ return keyProp.isIdentifier({ name: "description" }) || keyProp.isStringLiteral({ value: "description" });
73
+ })?.remove();
74
+ // Pre-parse or remove defaultMessage
75
+ if (defaultMessageProp) {
76
+ if (removeDefaultMessage) {
77
+ defaultMessageProp?.remove();
78
+ } else if (descriptor.defaultMessage) {
79
+ const valueProp = defaultMessageProp.get("value");
80
+ if (ast) {
81
+ valueProp.replaceWithSourceString(JSON.stringify(parse(descriptor.defaultMessage)));
82
+ } else {
83
+ valueProp.replaceWith(t.stringLiteral(descriptor.defaultMessage));
84
+ }
85
+ }
86
+ }
87
+ tagAsExtracted(path);
88
+ }
89
+ // Check that this is `defineMessages` call
90
+ if (callee.isIdentifier({ name: "defineMessages" }) || callee.isIdentifier({ name: "defineMessage" })) {
91
+ const firstArgument = args[0];
92
+ const messagesObj = getMessagesObjectFromExpression(firstArgument);
93
+ assertObjectExpression(messagesObj, callee);
94
+ if (callee.isIdentifier({ name: "defineMessage" })) {
95
+ processMessageObject(messagesObj);
96
+ } else {
97
+ const properties = messagesObj.get("properties");
98
+ if (Array.isArray(properties)) {
99
+ properties.map((prop) => prop.get("value")).forEach(processMessageObject);
100
+ }
101
+ }
102
+ }
103
+ // Check that this is `intl.formatMessage` call
104
+ if (isFormatMessageCall(callee, functionNames)) {
105
+ const messageDescriptor = args[0];
106
+ if (messageDescriptor && messageDescriptor.isObjectExpression()) {
107
+ processMessageObject(messageDescriptor);
108
+ }
109
+ }
121
110
  };
@@ -1,5 +1,5 @@
1
- import { PluginPass } from '@babel/core';
2
- import { State } from '../types.js';
3
- import * as t from '@babel/types';
4
- import { VisitNodeFunction } from '@babel/traverse';
1
+ import { type PluginPass } from "@babel/core";
2
+ import { type State } from "../types.js";
3
+ import * as t from "@babel/types";
4
+ import { type VisitNodeFunction } from "@babel/traverse";
5
5
  export declare const visitor: VisitNodeFunction<PluginPass & State, t.JSXOpeningElement>;
@@ -1,82 +1,74 @@
1
- import * as t from '@babel/types';
2
- import { parse } from '@formatjs/icu-messageformat-parser';
3
- import { createMessageDescriptor, evaluateMessageDescriptor, getMessageDescriptorKey, storeMessage, tagAsExtracted, wasExtracted, } from '../utils.js';
4
- export const visitor = function (path, { opts, file: { opts: { filename }, }, }) {
5
- const { removeDefaultMessage, idInterpolationPattern, overrideIdFn, ast, preserveWhitespace, flatten, } = opts;
6
- const { componentNames, messages } = this;
7
- if (wasExtracted(path)) {
8
- return;
9
- }
10
- const name = path.get('name');
11
- if (!componentNames.find(n => name.isJSXIdentifier({ name: n }))) {
12
- return;
13
- }
14
- const attributes = path
15
- .get('attributes')
16
- .filter(attr => attr.isJSXAttribute());
17
- const descriptorPath = createMessageDescriptor(attributes.map(attr => [
18
- attr.get('name'),
19
- attr.get('value'),
20
- ]));
21
- // In order for a default message to be extracted when
22
- // declaring a JSX element, it must be done with standard
23
- // `key=value` attributes. But it's completely valid to
24
- // write `<FormattedMessage {...descriptor} />`, because it will be
25
- // skipped here and extracted elsewhere. The descriptor will
26
- // be extracted only (storeMessage) if a `defaultMessage` prop.
27
- if (!descriptorPath.defaultMessage) {
28
- return;
29
- }
30
- // Evaluate the Message Descriptor values in a JSX
31
- // context, then store it.
32
- const descriptor = evaluateMessageDescriptor(descriptorPath, true, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten);
33
- storeMessage(descriptor, path, opts, filename || undefined, messages);
34
- let idAttr;
35
- let descriptionAttr;
36
- let defaultMessageAttr;
37
- const firstAttr = attributes[0];
38
- for (const attr of attributes) {
39
- if (!attr.isJSXAttribute()) {
40
- continue;
41
- }
42
- switch (getMessageDescriptorKey(attr.get('name'))) {
43
- case 'description':
44
- descriptionAttr = attr;
45
- break;
46
- case 'defaultMessage':
47
- defaultMessageAttr = attr;
48
- break;
49
- case 'id':
50
- idAttr = attr;
51
- break;
52
- }
53
- }
54
- // Insert ID before removing node to prevent null node insertBefore
55
- if (overrideIdFn || (descriptor.id && idInterpolationPattern)) {
56
- if (idAttr) {
57
- idAttr.get('value').replaceWith(t.stringLiteral(descriptor.id));
58
- }
59
- else if (firstAttr) {
60
- firstAttr.insertBefore(t.jsxAttribute(t.jsxIdentifier('id'), t.stringLiteral(descriptor.id)));
61
- }
62
- }
63
- if (descriptionAttr) {
64
- descriptionAttr.remove();
65
- }
66
- if (defaultMessageAttr) {
67
- if (removeDefaultMessage) {
68
- defaultMessageAttr.remove();
69
- }
70
- else if (ast && descriptor.defaultMessage) {
71
- defaultMessageAttr
72
- .get('value')
73
- .replaceWith(t.jsxExpressionContainer(t.nullLiteral()));
74
- const valueAttr = defaultMessageAttr.get('value');
75
- valueAttr
76
- .get('expression')
77
- .replaceWithSourceString(JSON.stringify(parse(descriptor.defaultMessage)));
78
- }
79
- }
80
- // Tag the AST node so we don't try to extract it twice.
81
- tagAsExtracted(path);
1
+ import "@babel/core";
2
+ import "../types.js";
3
+ import * as t from "@babel/types";
4
+ import "@babel/traverse";
5
+ import { parse } from "@formatjs/icu-messageformat-parser";
6
+ import { createMessageDescriptor, evaluateMessageDescriptor, getMessageDescriptorKey, storeMessage, tagAsExtracted, wasExtracted } from "../utils.js";
7
+ export const visitor = function(path, { opts, file: { opts: { filename } } }) {
8
+ const { removeDefaultMessage, idInterpolationPattern, overrideIdFn, ast, preserveWhitespace, flatten } = opts;
9
+ const { componentNames, messages } = this;
10
+ if (wasExtracted(path)) {
11
+ return;
12
+ }
13
+ const name = path.get("name");
14
+ if (!componentNames.find((n) => name.isJSXIdentifier({ name: n }))) {
15
+ return;
16
+ }
17
+ const attributes = path.get("attributes").filter((attr) => attr.isJSXAttribute());
18
+ const descriptorPath = createMessageDescriptor(attributes.map((attr) => [attr.get("name"), attr.get("value")]));
19
+ // In order for a default message to be extracted when
20
+ // declaring a JSX element, it must be done with standard
21
+ // `key=value` attributes. But it's completely valid to
22
+ // write `<FormattedMessage {...descriptor} />`, because it will be
23
+ // skipped here and extracted elsewhere. The descriptor will
24
+ // be extracted only (storeMessage) if a `defaultMessage` prop.
25
+ if (!descriptorPath.defaultMessage) {
26
+ return;
27
+ }
28
+ // Evaluate the Message Descriptor values in a JSX
29
+ // context, then store it.
30
+ const descriptor = evaluateMessageDescriptor(descriptorPath, true, filename || undefined, idInterpolationPattern, overrideIdFn, preserveWhitespace, flatten);
31
+ storeMessage(descriptor, path, opts, filename || undefined, messages);
32
+ let idAttr;
33
+ let descriptionAttr;
34
+ let defaultMessageAttr;
35
+ const firstAttr = attributes[0];
36
+ for (const attr of attributes) {
37
+ if (!attr.isJSXAttribute()) {
38
+ continue;
39
+ }
40
+ switch (getMessageDescriptorKey(attr.get("name"))) {
41
+ case "description":
42
+ descriptionAttr = attr;
43
+ break;
44
+ case "defaultMessage":
45
+ defaultMessageAttr = attr;
46
+ break;
47
+ case "id":
48
+ idAttr = attr;
49
+ break;
50
+ }
51
+ }
52
+ // Insert ID before removing node to prevent null node insertBefore
53
+ if (overrideIdFn || descriptor.id && idInterpolationPattern) {
54
+ if (idAttr) {
55
+ idAttr.get("value").replaceWith(t.stringLiteral(descriptor.id));
56
+ } else if (firstAttr) {
57
+ firstAttr.insertBefore(t.jsxAttribute(t.jsxIdentifier("id"), t.stringLiteral(descriptor.id)));
58
+ }
59
+ }
60
+ if (descriptionAttr) {
61
+ descriptionAttr.remove();
62
+ }
63
+ if (defaultMessageAttr) {
64
+ if (removeDefaultMessage) {
65
+ defaultMessageAttr.remove();
66
+ } else if (ast && descriptor.defaultMessage) {
67
+ defaultMessageAttr.get("value").replaceWith(t.jsxExpressionContainer(t.nullLiteral()));
68
+ const valueAttr = defaultMessageAttr.get("value");
69
+ valueAttr.get("expression").replaceWithSourceString(JSON.stringify(parse(descriptor.defaultMessage)));
70
+ }
71
+ }
72
+ // Tag the AST node so we don't try to extract it twice.
73
+ tagAsExtracted(path);
82
74
  };