@formatjs/ts-transformer 4.1.0 → 4.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/src/transform.js CHANGED
@@ -1,569 +1,459 @@
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 * as stringifyNs from 'json-stable-stringify';
5
- import * as typescript from 'typescript';
6
- import { debug } from './console_utils.js';
7
- import { interpolateName } from './interpolate-name.js';
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 * as stringifyNs from "json-stable-stringify";
5
+ import * as typescript from "typescript";
6
+ import { debug } from "./console_utils.js";
7
+ import { interpolateName } from "./interpolate-name.js";
8
+ import "./types.js";
8
9
  const stringify = stringifyNs.default || stringifyNs;
9
10
  const MESSAGE_DESC_KEYS = [
10
- 'id',
11
- 'defaultMessage',
12
- 'description',
11
+ "id",
12
+ "defaultMessage",
13
+ "description"
13
14
  ];
14
15
  function primitiveToTSNode(factory, v) {
15
- return typeof v === 'string'
16
- ? factory.createStringLiteral(v)
17
- : typeof v === 'number'
18
- ? factory.createNumericLiteral(v + '')
19
- : typeof v === 'boolean'
20
- ? v
21
- ? factory.createTrue()
22
- : factory.createFalse()
23
- : undefined;
16
+ return typeof v === "string" ? factory.createStringLiteral(v) : typeof v === "number" ? factory.createNumericLiteral(v + "") : typeof v === "boolean" ? v ? factory.createTrue() : factory.createFalse() : undefined;
24
17
  }
25
18
  function isValidIdentifier(k) {
26
- try {
27
- new Function(`return {${k}:1}`);
28
- return true;
29
- }
30
- catch {
31
- return false;
32
- }
19
+ try {
20
+ new Function(`return {${k}:1}`);
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
33
25
  }
34
26
  function objToTSNode(factory, obj) {
35
- if (typeof obj === 'object' && !obj) {
36
- return factory.createNull();
37
- }
38
- const props = Object.entries(obj)
39
- .filter(([_, v]) => typeof v !== 'undefined')
40
- .map(([k, v]) => factory.createPropertyAssignment(isValidIdentifier(k) ? k : factory.createStringLiteral(k), primitiveToTSNode(factory, v) ||
41
- (Array.isArray(v)
42
- ? factory.createArrayLiteralExpression(v
43
- .filter(n => typeof n !== 'undefined')
44
- .map(n => objToTSNode(factory, n)))
45
- : objToTSNode(factory, v))));
46
- return factory.createObjectLiteralExpression(props);
27
+ if (typeof obj === "object" && !obj) {
28
+ return factory.createNull();
29
+ }
30
+ const props = Object.entries(obj).filter(([_, v]) => typeof v !== "undefined").map(([k, v]) => factory.createPropertyAssignment(isValidIdentifier(k) ? k : factory.createStringLiteral(k), primitiveToTSNode(factory, v) || (Array.isArray(v) ? factory.createArrayLiteralExpression(v.filter((n) => typeof n !== "undefined").map((n) => objToTSNode(factory, n))) : objToTSNode(factory, v))));
31
+ return factory.createObjectLiteralExpression(props);
47
32
  }
48
33
  function messageASTToTSNode(factory, ast) {
49
- return factory.createArrayLiteralExpression(ast.map(el => objToTSNode(factory, el)));
34
+ return factory.createArrayLiteralExpression(ast.map((el) => objToTSNode(factory, el)));
50
35
  }
51
36
  function literalToObj(ts, n) {
52
- if (ts.isNumericLiteral(n)) {
53
- return +n.text;
54
- }
55
- if (ts.isStringLiteral(n)) {
56
- return n.text;
57
- }
58
- if (n.kind === ts.SyntaxKind.TrueKeyword) {
59
- return true;
60
- }
61
- if (n.kind === ts.SyntaxKind.FalseKeyword) {
62
- return false;
63
- }
37
+ if (ts.isNumericLiteral(n)) {
38
+ return +n.text;
39
+ }
40
+ if (ts.isStringLiteral(n)) {
41
+ return n.text;
42
+ }
43
+ if (n.kind === ts.SyntaxKind.TrueKeyword) {
44
+ return true;
45
+ }
46
+ if (n.kind === ts.SyntaxKind.FalseKeyword) {
47
+ return false;
48
+ }
64
49
  }
65
50
  function objectLiteralExpressionToObj(ts, obj) {
66
- return obj.properties.reduce((all, prop) => {
67
- if (ts.isPropertyAssignment(prop) && prop.name) {
68
- if (ts.isIdentifier(prop.name)) {
69
- all[prop.name.escapedText.toString()] = literalToObj(ts, prop.initializer);
70
- }
71
- else if (ts.isStringLiteral(prop.name)) {
72
- all[prop.name.text] = literalToObj(ts, prop.initializer);
73
- }
74
- }
75
- return all;
76
- }, {});
51
+ return obj.properties.reduce((all, prop) => {
52
+ if (ts.isPropertyAssignment(prop) && prop.name) {
53
+ if (ts.isIdentifier(prop.name)) {
54
+ all[prop.name.escapedText.toString()] = literalToObj(ts, prop.initializer);
55
+ } else if (ts.isStringLiteral(prop.name)) {
56
+ all[prop.name.text] = literalToObj(ts, prop.initializer);
57
+ }
58
+ }
59
+ return all;
60
+ }, {});
77
61
  }
78
62
  const DEFAULT_OPTS = {
79
- onMsgExtracted: () => undefined,
80
- onMetaExtracted: () => undefined,
63
+ onMsgExtracted: () => undefined,
64
+ onMetaExtracted: () => undefined
81
65
  };
82
66
  function isMultipleMessageDecl(ts, node) {
83
- return (ts.isIdentifier(node.expression) &&
84
- node.expression.text === 'defineMessages');
67
+ return ts.isIdentifier(node.expression) && node.expression.text === "defineMessages";
85
68
  }
86
69
  function isSingularMessageDecl(ts, node, additionalComponentNames) {
87
- const compNames = new Set([
88
- 'FormattedMessage',
89
- 'defineMessage',
90
- 'formatMessage',
91
- '$formatMessage',
92
- '$t',
93
- ...additionalComponentNames,
94
- ]);
95
- let fnName = '';
96
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
97
- fnName = node.expression.text;
98
- }
99
- else if (ts.isJsxOpeningElement(node) && ts.isIdentifier(node.tagName)) {
100
- fnName = node.tagName.text;
101
- }
102
- else if (ts.isJsxSelfClosingElement(node) &&
103
- ts.isIdentifier(node.tagName)) {
104
- fnName = node.tagName.text;
105
- }
106
- return compNames.has(fnName);
70
+ const compNames = new Set([
71
+ "FormattedMessage",
72
+ "defineMessage",
73
+ "formatMessage",
74
+ "$formatMessage",
75
+ "$t",
76
+ ...additionalComponentNames
77
+ ]);
78
+ let fnName = "";
79
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
80
+ fnName = node.expression.text;
81
+ } else if (ts.isJsxOpeningElement(node) && ts.isIdentifier(node.tagName)) {
82
+ fnName = node.tagName.text;
83
+ } else if (ts.isJsxSelfClosingElement(node) && ts.isIdentifier(node.tagName)) {
84
+ fnName = node.tagName.text;
85
+ }
86
+ return compNames.has(fnName);
107
87
  }
108
88
  function evaluateStringConcat(ts, node) {
109
- const { right, left } = node;
110
- if (!ts.isStringLiteral(right)) {
111
- return ['', false];
112
- }
113
- if (ts.isStringLiteral(left)) {
114
- return [left.text + right.text, true];
115
- }
116
- if (ts.isBinaryExpression(left)) {
117
- const [result, isStatic] = evaluateStringConcat(ts, left);
118
- return [result + right.text, isStatic];
119
- }
120
- return ['', false];
89
+ const { right, left } = node;
90
+ if (!ts.isStringLiteral(right)) {
91
+ return ["", false];
92
+ }
93
+ if (ts.isStringLiteral(left)) {
94
+ return [left.text + right.text, true];
95
+ }
96
+ if (ts.isBinaryExpression(left)) {
97
+ const [result, isStatic] = evaluateStringConcat(ts, left);
98
+ return [result + right.text, isStatic];
99
+ }
100
+ return ["", false];
121
101
  }
122
102
  function extractMessageDescriptor(ts, node, { overrideIdFn, extractSourceLocation, preserveWhitespace, flatten }, sf) {
123
- let properties = undefined;
124
- if (ts.isObjectLiteralExpression(node)) {
125
- properties = node.properties;
126
- }
127
- else if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
128
- properties = node.attributes.properties;
129
- }
130
- const msg = { id: '' };
131
- if (!properties) {
132
- return;
133
- }
134
- properties.forEach(prop => {
135
- const { name } = prop;
136
- const initializer = ts.isPropertyAssignment(prop) || ts.isJsxAttribute(prop)
137
- ? prop.initializer
138
- : undefined;
139
- if (name && ts.isIdentifier(name) && initializer) {
140
- // {id: 'id'}
141
- if (ts.isStringLiteral(initializer)) {
142
- switch (name.text) {
143
- case 'id':
144
- msg.id = initializer.text;
145
- break;
146
- case 'defaultMessage':
147
- msg.defaultMessage = initializer.text;
148
- break;
149
- case 'description':
150
- msg.description = initializer.text;
151
- break;
152
- }
153
- }
154
- // {id: `id`}
155
- else if (ts.isNoSubstitutionTemplateLiteral(initializer)) {
156
- switch (name.text) {
157
- case 'id':
158
- msg.id = initializer.text;
159
- break;
160
- case 'defaultMessage':
161
- msg.defaultMessage = initializer.text;
162
- break;
163
- case 'description':
164
- msg.description = initializer.text;
165
- break;
166
- }
167
- }
168
- // {id: dedent`id`}
169
- // GH #5069: Only check for substitutions on message-related props
170
- else if (ts.isTaggedTemplateExpression(initializer)) {
171
- const isMessageProp = name.text === 'id' ||
172
- name.text === 'defaultMessage' ||
173
- name.text === 'description';
174
- if (!isMessageProp) {
175
- // Skip non-message props (like tagName, values, etc.)
176
- return;
177
- }
178
- const { template } = initializer;
179
- if (!ts.isNoSubstitutionTemplateLiteral(template)) {
180
- throw new Error('Tagged template expression must be no substitution');
181
- }
182
- switch (name.text) {
183
- case 'id':
184
- msg.id = template.text;
185
- break;
186
- case 'defaultMessage':
187
- msg.defaultMessage = template.text;
188
- break;
189
- case 'description':
190
- msg.description = template.text;
191
- break;
192
- }
193
- }
194
- else if (ts.isJsxExpression(initializer) && initializer.expression) {
195
- // <FormattedMessage foo={'barbaz'} />
196
- if (ts.isStringLiteral(initializer.expression)) {
197
- switch (name.text) {
198
- case 'id':
199
- msg.id = initializer.expression.text;
200
- break;
201
- case 'defaultMessage':
202
- msg.defaultMessage = initializer.expression.text;
203
- break;
204
- case 'description':
205
- msg.description = initializer.expression.text;
206
- break;
207
- }
208
- }
209
- // description={{custom: 1}}
210
- else if (ts.isObjectLiteralExpression(initializer.expression) &&
211
- name.text === 'description') {
212
- msg.description = objectLiteralExpressionToObj(ts, initializer.expression);
213
- }
214
- // <FormattedMessage foo={`bar`} />
215
- else if (ts.isNoSubstitutionTemplateLiteral(initializer.expression)) {
216
- const { expression } = initializer;
217
- switch (name.text) {
218
- case 'id':
219
- msg.id = expression.text;
220
- break;
221
- case 'defaultMessage':
222
- msg.defaultMessage = expression.text;
223
- break;
224
- case 'description':
225
- msg.description = expression.text;
226
- break;
227
- }
228
- }
229
- // <FormattedMessage foo={dedent`dedent Hello World!`} />
230
- // GH #5069: Only check for substitutions on message-related props
231
- else if (ts.isTaggedTemplateExpression(initializer.expression)) {
232
- const isMessageProp = name.text === 'id' ||
233
- name.text === 'defaultMessage' ||
234
- name.text === 'description';
235
- if (!isMessageProp) {
236
- // Skip non-message props (like tagName, values, etc.)
237
- return;
238
- }
239
- const { expression: { template }, } = initializer;
240
- if (!ts.isNoSubstitutionTemplateLiteral(template)) {
241
- throw new Error('Tagged template expression must be no substitution');
242
- }
243
- switch (name.text) {
244
- case 'id':
245
- msg.id = template.text;
246
- break;
247
- case 'defaultMessage':
248
- msg.defaultMessage = template.text;
249
- break;
250
- case 'description':
251
- msg.description = template.text;
252
- break;
253
- }
254
- }
255
- // <FormattedMessage foo={'bar' + 'baz'} />
256
- else if (ts.isBinaryExpression(initializer.expression)) {
257
- const { expression } = initializer;
258
- const [result, isStatic] = evaluateStringConcat(ts, expression);
259
- if (isStatic) {
260
- switch (name.text) {
261
- case 'id':
262
- msg.id = result;
263
- break;
264
- case 'defaultMessage':
265
- msg.defaultMessage = result;
266
- break;
267
- case 'description':
268
- msg.description = result;
269
- break;
270
- }
271
- }
272
- else if (MESSAGE_DESC_KEYS.includes(name.text) &&
273
- name.text !== 'description') {
274
- // Non-static expression for defaultMessage or id
275
- throw new Error(`[FormatJS] \`${name.text}\` must be a string literal or statically evaluable expression to be extracted.`);
276
- }
277
- }
278
- // Non-static JSX expression for defaultMessage or id
279
- else if (MESSAGE_DESC_KEYS.includes(name.text) &&
280
- name.text !== 'description') {
281
- throw new Error(`[FormatJS] \`${name.text}\` must be a string literal to be extracted.`);
282
- }
283
- }
284
- // {defaultMessage: 'asd' + bar'}
285
- else if (ts.isBinaryExpression(initializer)) {
286
- const [result, isStatic] = evaluateStringConcat(ts, initializer);
287
- if (isStatic) {
288
- switch (name.text) {
289
- case 'id':
290
- msg.id = result;
291
- break;
292
- case 'defaultMessage':
293
- msg.defaultMessage = result;
294
- break;
295
- case 'description':
296
- msg.description = result;
297
- break;
298
- }
299
- }
300
- else if (MESSAGE_DESC_KEYS.includes(name.text) &&
301
- name.text !== 'description') {
302
- // Non-static expression for defaultMessage or id
303
- throw new Error(`[FormatJS] \`${name.text}\` must be a string literal or statically evaluable expression to be extracted.`);
304
- }
305
- }
306
- // description: {custom: 1}
307
- else if (ts.isObjectLiteralExpression(initializer) &&
308
- name.text === 'description') {
309
- msg.description = objectLiteralExpressionToObj(ts, initializer);
310
- }
311
- // Non-static value for defaultMessage or id
312
- else if (MESSAGE_DESC_KEYS.includes(name.text) &&
313
- name.text !== 'description') {
314
- throw new Error(`[FormatJS] \`${name.text}\` must be a string literal to be extracted.`);
315
- }
316
- }
317
- });
318
- // We extracted nothing
319
- if (!msg.defaultMessage && !msg.id) {
320
- return;
321
- }
322
- if (msg.defaultMessage && !preserveWhitespace) {
323
- msg.defaultMessage = msg.defaultMessage.trim().replace(/\s+/gm, ' ');
324
- }
325
- // GH #3537: Apply flatten transformation before calling overrideIdFn
326
- // so that the ID generation sees the same message format as the final output
327
- if (flatten && msg.defaultMessage) {
328
- try {
329
- msg.defaultMessage = printAST(hoistSelectors(parse(msg.defaultMessage)));
330
- }
331
- catch (e) {
332
- // If flatten fails, continue with original message
333
- // The error will be caught again during validation
334
- }
335
- }
336
- if (msg.defaultMessage && overrideIdFn) {
337
- switch (typeof overrideIdFn) {
338
- case 'string':
339
- if (!msg.id) {
340
- msg.id = interpolateName({ resourcePath: sf.fileName }, overrideIdFn, {
341
- content: msg.description
342
- ? `${msg.defaultMessage}#${typeof msg.description === 'string'
343
- ? msg.description
344
- : stringify(msg.description)}`
345
- : msg.defaultMessage,
346
- });
347
- }
348
- break;
349
- case 'function':
350
- msg.id = overrideIdFn(msg.id, msg.defaultMessage, msg.description, sf.fileName);
351
- break;
352
- }
353
- }
354
- if (extractSourceLocation) {
355
- return {
356
- ...msg,
357
- file: sf.fileName,
358
- start: node.pos,
359
- end: node.end,
360
- };
361
- }
362
- return msg;
103
+ let properties = undefined;
104
+ if (ts.isObjectLiteralExpression(node)) {
105
+ properties = node.properties;
106
+ } else if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
107
+ properties = node.attributes.properties;
108
+ }
109
+ const msg = { id: "" };
110
+ if (!properties) {
111
+ return;
112
+ }
113
+ properties.forEach((prop) => {
114
+ const { name } = prop;
115
+ const initializer = ts.isPropertyAssignment(prop) || ts.isJsxAttribute(prop) ? prop.initializer : undefined;
116
+ if (name && ts.isIdentifier(name) && initializer) {
117
+ // {id: 'id'}
118
+ if (ts.isStringLiteral(initializer)) {
119
+ switch (name.text) {
120
+ case "id":
121
+ msg.id = initializer.text;
122
+ break;
123
+ case "defaultMessage":
124
+ msg.defaultMessage = initializer.text;
125
+ break;
126
+ case "description":
127
+ msg.description = initializer.text;
128
+ break;
129
+ }
130
+ } else if (ts.isNoSubstitutionTemplateLiteral(initializer)) {
131
+ switch (name.text) {
132
+ case "id":
133
+ msg.id = initializer.text;
134
+ break;
135
+ case "defaultMessage":
136
+ msg.defaultMessage = initializer.text;
137
+ break;
138
+ case "description":
139
+ msg.description = initializer.text;
140
+ break;
141
+ }
142
+ } else if (ts.isTaggedTemplateExpression(initializer)) {
143
+ const isMessageProp = name.text === "id" || name.text === "defaultMessage" || name.text === "description";
144
+ if (!isMessageProp) {
145
+ // Skip non-message props (like tagName, values, etc.)
146
+ return;
147
+ }
148
+ const { template } = initializer;
149
+ if (!ts.isNoSubstitutionTemplateLiteral(template)) {
150
+ throw new Error("Tagged template expression must be no substitution");
151
+ }
152
+ switch (name.text) {
153
+ case "id":
154
+ msg.id = template.text;
155
+ break;
156
+ case "defaultMessage":
157
+ msg.defaultMessage = template.text;
158
+ break;
159
+ case "description":
160
+ msg.description = template.text;
161
+ break;
162
+ }
163
+ } else if (ts.isJsxExpression(initializer) && initializer.expression) {
164
+ // <FormattedMessage foo={'barbaz'} />
165
+ if (ts.isStringLiteral(initializer.expression)) {
166
+ switch (name.text) {
167
+ case "id":
168
+ msg.id = initializer.expression.text;
169
+ break;
170
+ case "defaultMessage":
171
+ msg.defaultMessage = initializer.expression.text;
172
+ break;
173
+ case "description":
174
+ msg.description = initializer.expression.text;
175
+ break;
176
+ }
177
+ } else if (ts.isObjectLiteralExpression(initializer.expression) && name.text === "description") {
178
+ msg.description = objectLiteralExpressionToObj(ts, initializer.expression);
179
+ } else if (ts.isNoSubstitutionTemplateLiteral(initializer.expression)) {
180
+ const { expression } = initializer;
181
+ switch (name.text) {
182
+ case "id":
183
+ msg.id = expression.text;
184
+ break;
185
+ case "defaultMessage":
186
+ msg.defaultMessage = expression.text;
187
+ break;
188
+ case "description":
189
+ msg.description = expression.text;
190
+ break;
191
+ }
192
+ } else if (ts.isTaggedTemplateExpression(initializer.expression)) {
193
+ const isMessageProp = name.text === "id" || name.text === "defaultMessage" || name.text === "description";
194
+ if (!isMessageProp) {
195
+ // Skip non-message props (like tagName, values, etc.)
196
+ return;
197
+ }
198
+ const { expression: { template } } = initializer;
199
+ if (!ts.isNoSubstitutionTemplateLiteral(template)) {
200
+ throw new Error("Tagged template expression must be no substitution");
201
+ }
202
+ switch (name.text) {
203
+ case "id":
204
+ msg.id = template.text;
205
+ break;
206
+ case "defaultMessage":
207
+ msg.defaultMessage = template.text;
208
+ break;
209
+ case "description":
210
+ msg.description = template.text;
211
+ break;
212
+ }
213
+ } else if (ts.isBinaryExpression(initializer.expression)) {
214
+ const { expression } = initializer;
215
+ const [result, isStatic] = evaluateStringConcat(ts, expression);
216
+ if (isStatic) {
217
+ switch (name.text) {
218
+ case "id":
219
+ msg.id = result;
220
+ break;
221
+ case "defaultMessage":
222
+ msg.defaultMessage = result;
223
+ break;
224
+ case "description":
225
+ msg.description = result;
226
+ break;
227
+ }
228
+ } else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
229
+ // Non-static expression for defaultMessage or id
230
+ throw new Error(`[FormatJS] \`${name.text}\` must be a string literal or statically evaluable expression to be extracted.`);
231
+ }
232
+ } else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
233
+ throw new Error(`[FormatJS] \`${name.text}\` must be a string literal to be extracted.`);
234
+ }
235
+ } else if (ts.isBinaryExpression(initializer)) {
236
+ const [result, isStatic] = evaluateStringConcat(ts, initializer);
237
+ if (isStatic) {
238
+ switch (name.text) {
239
+ case "id":
240
+ msg.id = result;
241
+ break;
242
+ case "defaultMessage":
243
+ msg.defaultMessage = result;
244
+ break;
245
+ case "description":
246
+ msg.description = result;
247
+ break;
248
+ }
249
+ } else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
250
+ // Non-static expression for defaultMessage or id
251
+ throw new Error(`[FormatJS] \`${name.text}\` must be a string literal or statically evaluable expression to be extracted.`);
252
+ }
253
+ } else if (ts.isObjectLiteralExpression(initializer) && name.text === "description") {
254
+ msg.description = objectLiteralExpressionToObj(ts, initializer);
255
+ } else if (MESSAGE_DESC_KEYS.includes(name.text) && name.text !== "description") {
256
+ throw new Error(`[FormatJS] \`${name.text}\` must be a string literal to be extracted.`);
257
+ }
258
+ }
259
+ });
260
+ // We extracted nothing
261
+ if (!msg.defaultMessage && !msg.id) {
262
+ return;
263
+ }
264
+ if (msg.defaultMessage && !preserveWhitespace) {
265
+ msg.defaultMessage = msg.defaultMessage.trim().replace(/\s+/gm, " ");
266
+ }
267
+ // GH #3537: Apply flatten transformation before calling overrideIdFn
268
+ // so that the ID generation sees the same message format as the final output
269
+ if (flatten && msg.defaultMessage) {
270
+ try {
271
+ msg.defaultMessage = printAST(hoistSelectors(parse(msg.defaultMessage)));
272
+ } catch {}
273
+ }
274
+ if (msg.defaultMessage && overrideIdFn) {
275
+ switch (typeof overrideIdFn) {
276
+ case "string":
277
+ if (!msg.id) {
278
+ msg.id = interpolateName({ resourcePath: sf.fileName }, overrideIdFn, { content: msg.description ? `${msg.defaultMessage}#${typeof msg.description === "string" ? msg.description : stringify(msg.description)}` : msg.defaultMessage });
279
+ }
280
+ break;
281
+ case "function":
282
+ msg.id = overrideIdFn(msg.id, msg.defaultMessage, msg.description, sf.fileName);
283
+ break;
284
+ }
285
+ }
286
+ if (extractSourceLocation) {
287
+ return {
288
+ ...msg,
289
+ file: sf.fileName,
290
+ start: node.pos,
291
+ end: node.end
292
+ };
293
+ }
294
+ return msg;
363
295
  }
364
296
  /**
365
- * Check if node is `foo.bar.formatMessage` node
366
- * @param node
367
- * @param sf
368
- */
297
+ * Check if node is `foo.bar.formatMessage` node
298
+ * @param node
299
+ * @param sf
300
+ */
369
301
  function isMemberMethodFormatMessageCall(ts, node, additionalFunctionNames) {
370
- const fnNames = new Set([
371
- 'formatMessage',
372
- '$formatMessage',
373
- ...additionalFunctionNames,
374
- ]);
375
- const method = node.expression;
376
- // Handle foo.formatMessage()
377
- if (ts.isPropertyAccessExpression(method)) {
378
- return fnNames.has(method.name.text);
379
- }
380
- // GH #4471: Handle foo.formatMessage<T>?.() - when both generics and optional chaining are used
381
- // TypeScript represents this as ExpressionWithTypeArguments containing a PropertyAccessExpression
382
- if (ts.isExpressionWithTypeArguments &&
383
- ts.isExpressionWithTypeArguments(method)) {
384
- const expr = method.expression;
385
- if (ts.isPropertyAccessExpression(expr)) {
386
- return fnNames.has(expr.name.text);
387
- }
388
- }
389
- // Handle formatMessage()
390
- return ts.isIdentifier(method) && fnNames.has(method.text);
302
+ const fnNames = new Set([
303
+ "formatMessage",
304
+ "$formatMessage",
305
+ ...additionalFunctionNames
306
+ ]);
307
+ const method = node.expression;
308
+ // Handle foo.formatMessage()
309
+ if (ts.isPropertyAccessExpression(method)) {
310
+ return fnNames.has(method.name.text);
311
+ }
312
+ // GH #4471: Handle foo.formatMessage<T>?.() - when both generics and optional chaining are used
313
+ // TypeScript represents this as ExpressionWithTypeArguments containing a PropertyAccessExpression
314
+ if (ts.isExpressionWithTypeArguments && ts.isExpressionWithTypeArguments(method)) {
315
+ const expr = method.expression;
316
+ if (ts.isPropertyAccessExpression(expr)) {
317
+ return fnNames.has(expr.name.text);
318
+ }
319
+ }
320
+ // Handle formatMessage()
321
+ return ts.isIdentifier(method) && fnNames.has(method.text);
391
322
  }
392
323
  function extractMessageFromJsxComponent(ts, factory, node, opts, sf) {
393
- const { onMsgExtracted } = opts;
394
- if (!isSingularMessageDecl(ts, node, opts.additionalComponentNames || [])) {
395
- return node;
396
- }
397
- const msg = extractMessageDescriptor(ts, node, opts, sf);
398
- if (!msg) {
399
- return node;
400
- }
401
- if (typeof onMsgExtracted === 'function') {
402
- onMsgExtracted(sf.fileName, [msg]);
403
- }
404
- const newProps = generateNewProperties(ts, factory, node.attributes, {
405
- defaultMessage: opts.removeDefaultMessage
406
- ? undefined
407
- : msg.defaultMessage,
408
- id: msg.id,
409
- }, opts.ast);
410
- if (ts.isJsxOpeningElement(node)) {
411
- return factory.updateJsxOpeningElement(node, node.tagName, node.typeArguments, factory.createJsxAttributes(newProps));
412
- }
413
- return factory.updateJsxSelfClosingElement(node, node.tagName, node.typeArguments, factory.createJsxAttributes(newProps));
324
+ const { onMsgExtracted } = opts;
325
+ if (!isSingularMessageDecl(ts, node, opts.additionalComponentNames || [])) {
326
+ return node;
327
+ }
328
+ const msg = extractMessageDescriptor(ts, node, opts, sf);
329
+ if (!msg) {
330
+ return node;
331
+ }
332
+ if (typeof onMsgExtracted === "function") {
333
+ onMsgExtracted(sf.fileName, [msg]);
334
+ }
335
+ const newProps = generateNewProperties(ts, factory, node.attributes, {
336
+ defaultMessage: opts.removeDefaultMessage ? undefined : msg.defaultMessage,
337
+ id: msg.id
338
+ }, opts.ast);
339
+ if (ts.isJsxOpeningElement(node)) {
340
+ return factory.updateJsxOpeningElement(node, node.tagName, node.typeArguments, factory.createJsxAttributes(newProps));
341
+ }
342
+ return factory.updateJsxSelfClosingElement(node, node.tagName, node.typeArguments, factory.createJsxAttributes(newProps));
414
343
  }
415
344
  function setAttributesInObject(ts, factory, node, msg, ast) {
416
- const newProps = [
417
- factory.createPropertyAssignment('id', factory.createStringLiteral(msg.id)),
418
- ...(msg.defaultMessage
419
- ? [
420
- factory.createPropertyAssignment('defaultMessage', ast
421
- ? messageASTToTSNode(factory, parse(msg.defaultMessage))
422
- : factory.createStringLiteral(msg.defaultMessage)),
423
- ]
424
- : []),
425
- ];
426
- for (const prop of node.properties) {
427
- if (ts.isPropertyAssignment(prop) &&
428
- ts.isIdentifier(prop.name) &&
429
- MESSAGE_DESC_KEYS.includes(prop.name.text)) {
430
- continue;
431
- }
432
- if (ts.isPropertyAssignment(prop)) {
433
- newProps.push(prop);
434
- }
435
- }
436
- return factory.createObjectLiteralExpression(factory.createNodeArray(newProps));
345
+ const newProps = [factory.createPropertyAssignment("id", factory.createStringLiteral(msg.id)), ...msg.defaultMessage ? [factory.createPropertyAssignment("defaultMessage", ast ? messageASTToTSNode(factory, parse(msg.defaultMessage)) : factory.createStringLiteral(msg.defaultMessage))] : []];
346
+ for (const prop of node.properties) {
347
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && MESSAGE_DESC_KEYS.includes(prop.name.text)) {
348
+ continue;
349
+ }
350
+ if (ts.isPropertyAssignment(prop)) {
351
+ newProps.push(prop);
352
+ }
353
+ }
354
+ return factory.createObjectLiteralExpression(factory.createNodeArray(newProps));
437
355
  }
438
356
  function generateNewProperties(ts, factory, node, msg, ast) {
439
- const newProps = [
440
- factory.createJsxAttribute(factory.createIdentifier('id'), factory.createStringLiteral(msg.id)),
441
- ...(msg.defaultMessage
442
- ? [
443
- factory.createJsxAttribute(factory.createIdentifier('defaultMessage'), ast
444
- ? factory.createJsxExpression(undefined, messageASTToTSNode(factory, parse(msg.defaultMessage)))
445
- : factory.createStringLiteral(msg.defaultMessage)),
446
- ]
447
- : []),
448
- ];
449
- for (const prop of node.properties) {
450
- if (ts.isJsxAttribute(prop) &&
451
- ts.isIdentifier(prop.name) &&
452
- MESSAGE_DESC_KEYS.includes(prop.name.text)) {
453
- continue;
454
- }
455
- if (ts.isJsxAttribute(prop)) {
456
- newProps.push(prop);
457
- }
458
- }
459
- return newProps;
357
+ const newProps = [factory.createJsxAttribute(factory.createIdentifier("id"), factory.createStringLiteral(msg.id)), ...msg.defaultMessage ? [factory.createJsxAttribute(factory.createIdentifier("defaultMessage"), ast ? factory.createJsxExpression(undefined, messageASTToTSNode(factory, parse(msg.defaultMessage))) : factory.createStringLiteral(msg.defaultMessage))] : []];
358
+ for (const prop of node.properties) {
359
+ if (ts.isJsxAttribute(prop) && ts.isIdentifier(prop.name) && MESSAGE_DESC_KEYS.includes(prop.name.text)) {
360
+ continue;
361
+ }
362
+ if (ts.isJsxAttribute(prop)) {
363
+ newProps.push(prop);
364
+ }
365
+ }
366
+ return newProps;
460
367
  }
461
368
  function extractMessagesFromCallExpression(ts, factory, node, opts, sf) {
462
- const { onMsgExtracted, additionalFunctionNames } = opts;
463
- if (isMultipleMessageDecl(ts, node)) {
464
- const [arg, ...restArgs] = node.arguments;
465
- let descriptorsObj;
466
- if (ts.isObjectLiteralExpression(arg)) {
467
- descriptorsObj = arg;
468
- }
469
- else if (ts.isAsExpression(arg) &&
470
- ts.isObjectLiteralExpression(arg.expression)) {
471
- descriptorsObj = arg.expression;
472
- }
473
- if (descriptorsObj) {
474
- const properties = descriptorsObj.properties;
475
- const msgs = properties
476
- .filter((prop) => ts.isPropertyAssignment(prop))
477
- .map(prop => ts.isObjectLiteralExpression(prop.initializer) &&
478
- extractMessageDescriptor(ts, prop.initializer, opts, sf))
479
- .filter((msg) => !!msg);
480
- if (!msgs.length) {
481
- return node;
482
- }
483
- debug('Multiple messages extracted from "%s": %s', sf.fileName, msgs);
484
- if (typeof onMsgExtracted === 'function') {
485
- onMsgExtracted(sf.fileName, msgs);
486
- }
487
- const clonedProperties = factory.createNodeArray(properties.map((prop, i) => {
488
- if (!ts.isPropertyAssignment(prop) ||
489
- !ts.isObjectLiteralExpression(prop.initializer)) {
490
- return prop;
491
- }
492
- return factory.createPropertyAssignment(prop.name, setAttributesInObject(ts, factory, prop.initializer, {
493
- defaultMessage: opts.removeDefaultMessage
494
- ? undefined
495
- : msgs[i].defaultMessage,
496
- id: msgs[i] ? msgs[i].id : '',
497
- }, opts.ast));
498
- }));
499
- const clonedDescriptorsObj = factory.createObjectLiteralExpression(clonedProperties);
500
- return factory.updateCallExpression(node, node.expression, node.typeArguments, [clonedDescriptorsObj, ...restArgs]);
501
- }
502
- }
503
- else if (isSingularMessageDecl(ts, node, opts.additionalComponentNames || []) ||
504
- isMemberMethodFormatMessageCall(ts, node, additionalFunctionNames || [])) {
505
- const [descriptorsObj, ...restArgs] = node.arguments;
506
- if (ts.isObjectLiteralExpression(descriptorsObj)) {
507
- const msg = extractMessageDescriptor(ts, descriptorsObj, opts, sf);
508
- if (!msg) {
509
- return node;
510
- }
511
- debug('Message extracted from "%s": %s', sf.fileName, msg);
512
- if (typeof onMsgExtracted === 'function') {
513
- onMsgExtracted(sf.fileName, [msg]);
514
- }
515
- return factory.updateCallExpression(node, node.expression, node.typeArguments, [
516
- setAttributesInObject(ts, factory, descriptorsObj, {
517
- defaultMessage: opts.removeDefaultMessage
518
- ? undefined
519
- : msg.defaultMessage,
520
- id: msg.id,
521
- }, opts.ast),
522
- ...restArgs,
523
- ]);
524
- }
525
- }
526
- return node;
369
+ const { onMsgExtracted, additionalFunctionNames } = opts;
370
+ if (isMultipleMessageDecl(ts, node)) {
371
+ const [arg, ...restArgs] = node.arguments;
372
+ let descriptorsObj;
373
+ if (ts.isObjectLiteralExpression(arg)) {
374
+ descriptorsObj = arg;
375
+ } else if (ts.isAsExpression(arg) && ts.isObjectLiteralExpression(arg.expression)) {
376
+ descriptorsObj = arg.expression;
377
+ }
378
+ if (descriptorsObj) {
379
+ const properties = descriptorsObj.properties;
380
+ const msgs = properties.filter((prop) => ts.isPropertyAssignment(prop)).map((prop) => ts.isObjectLiteralExpression(prop.initializer) && extractMessageDescriptor(ts, prop.initializer, opts, sf)).filter((msg) => !!msg);
381
+ if (!msgs.length) {
382
+ return node;
383
+ }
384
+ debug("Multiple messages extracted from \"%s\": %s", sf.fileName, msgs);
385
+ if (typeof onMsgExtracted === "function") {
386
+ onMsgExtracted(sf.fileName, msgs);
387
+ }
388
+ const clonedProperties = factory.createNodeArray(properties.map((prop, i) => {
389
+ if (!ts.isPropertyAssignment(prop) || !ts.isObjectLiteralExpression(prop.initializer)) {
390
+ return prop;
391
+ }
392
+ return factory.createPropertyAssignment(prop.name, setAttributesInObject(ts, factory, prop.initializer, {
393
+ defaultMessage: opts.removeDefaultMessage ? undefined : msgs[i].defaultMessage,
394
+ id: msgs[i] ? msgs[i].id : ""
395
+ }, opts.ast));
396
+ }));
397
+ const clonedDescriptorsObj = factory.createObjectLiteralExpression(clonedProperties);
398
+ return factory.updateCallExpression(node, node.expression, node.typeArguments, [clonedDescriptorsObj, ...restArgs]);
399
+ }
400
+ } else if (isSingularMessageDecl(ts, node, opts.additionalComponentNames || []) || isMemberMethodFormatMessageCall(ts, node, additionalFunctionNames || [])) {
401
+ const [descriptorsObj, ...restArgs] = node.arguments;
402
+ if (ts.isObjectLiteralExpression(descriptorsObj)) {
403
+ const msg = extractMessageDescriptor(ts, descriptorsObj, opts, sf);
404
+ if (!msg) {
405
+ return node;
406
+ }
407
+ debug("Message extracted from \"%s\": %s", sf.fileName, msg);
408
+ if (typeof onMsgExtracted === "function") {
409
+ onMsgExtracted(sf.fileName, [msg]);
410
+ }
411
+ return factory.updateCallExpression(node, node.expression, node.typeArguments, [setAttributesInObject(ts, factory, descriptorsObj, {
412
+ defaultMessage: opts.removeDefaultMessage ? undefined : msg.defaultMessage,
413
+ id: msg.id
414
+ }, opts.ast), ...restArgs]);
415
+ }
416
+ }
417
+ return node;
527
418
  }
528
419
  const PRAGMA_REGEX = /^\/\/ @([^\s]*) (.*)$/m;
529
420
  function getVisitor(ts, ctx, sf, opts) {
530
- const visitor = (node) => {
531
- const newNode = ts.isCallExpression(node)
532
- ? extractMessagesFromCallExpression(ts, ctx.factory, node, opts, sf)
533
- : ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)
534
- ? extractMessageFromJsxComponent(ts, ctx.factory, node, opts, sf)
535
- : node;
536
- return ts.visitEachChild(newNode, visitor, ctx);
537
- };
538
- return visitor;
421
+ const visitor = (node) => {
422
+ const newNode = ts.isCallExpression(node) ? extractMessagesFromCallExpression(ts, ctx.factory, node, opts, sf) : ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node) ? extractMessageFromJsxComponent(ts, ctx.factory, node, opts, sf) : node;
423
+ return ts.visitEachChild(newNode, visitor, ctx);
424
+ };
425
+ return visitor;
539
426
  }
540
427
  export function transformWithTs(ts, opts) {
541
- opts = { ...DEFAULT_OPTS, ...opts };
542
- debug('Transforming options', opts);
543
- const transformFn = ctx => {
544
- return sf => {
545
- const pragmaResult = PRAGMA_REGEX.exec(sf.text);
546
- if (pragmaResult) {
547
- debug('Pragma found', pragmaResult);
548
- const [, pragma, kvString] = pragmaResult;
549
- if (pragma === opts.pragma) {
550
- const kvs = kvString.split(' ');
551
- const result = {};
552
- for (const kv of kvs) {
553
- const [k, v] = kv.split(':');
554
- result[k] = v;
555
- }
556
- debug('Pragma extracted', result);
557
- if (typeof opts.onMetaExtracted === 'function') {
558
- opts.onMetaExtracted(sf.fileName, result);
559
- }
560
- }
561
- }
562
- return ts.visitEachChild(sf, getVisitor(ts, ctx, sf, opts), ctx);
563
- };
564
- };
565
- return transformFn;
428
+ opts = {
429
+ ...DEFAULT_OPTS,
430
+ ...opts
431
+ };
432
+ debug("Transforming options", opts);
433
+ const transformFn = (ctx) => {
434
+ return (sf) => {
435
+ const pragmaResult = PRAGMA_REGEX.exec(sf.text);
436
+ if (pragmaResult) {
437
+ debug("Pragma found", pragmaResult);
438
+ const [, pragma, kvString] = pragmaResult;
439
+ if (pragma === opts.pragma) {
440
+ const kvs = kvString.split(" ");
441
+ const result = {};
442
+ for (const kv of kvs) {
443
+ const [k, v] = kv.split(":");
444
+ result[k] = v;
445
+ }
446
+ debug("Pragma extracted", result);
447
+ if (typeof opts.onMetaExtracted === "function") {
448
+ opts.onMetaExtracted(sf.fileName, result);
449
+ }
450
+ }
451
+ }
452
+ return ts.visitEachChild(sf, getVisitor(ts, ctx, sf, opts), ctx);
453
+ };
454
+ };
455
+ return transformFn;
566
456
  }
567
457
  export function transform(opts) {
568
- return transformWithTs(typescript, opts);
458
+ return transformWithTs(typescript, opts);
569
459
  }