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