@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/index.d.ts +3 -3
- package/index.js +3 -3
- package/package.json +2 -2
- package/src/console_utils.js +15 -15
- package/src/interpolate-name.d.ts +8 -8
- package/src/interpolate-name.js +68 -83
- package/src/transform.d.ts +73 -69
- package/src/transform.js +417 -469
- package/src/types.d.ts +7 -7
- package/ts-jest-integration.d.ts +3 -3
- package/ts-jest-integration.js +5 -4
package/src/transform.js
CHANGED
|
@@ -1,511 +1,459 @@
|
|
|
1
|
-
import { parse } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
"id",
|
|
12
|
+
"defaultMessage",
|
|
13
|
+
"description"
|
|
11
14
|
];
|
|
12
15
|
function primitiveToTSNode(factory, v) {
|
|
13
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
34
|
+
return factory.createArrayLiteralExpression(ast.map((el) => objToTSNode(factory, el)));
|
|
48
35
|
}
|
|
49
36
|
function literalToObj(ts, n) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
78
|
-
|
|
63
|
+
onMsgExtracted: () => undefined,
|
|
64
|
+
onMetaExtracted: () => undefined
|
|
79
65
|
};
|
|
80
66
|
function isMultipleMessageDecl(ts, node) {
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
458
|
+
return transformWithTs(typescript, opts);
|
|
511
459
|
}
|