1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | Object.defineProperty(exports, "__esModule", { value: true });
|
8 | var tslib_1 = require("tslib");
|
9 | var intl_messageformat_parser_1 = require("intl-messageformat-parser");
|
10 | var helper_plugin_utils_1 = require("@babel/helper-plugin-utils");
|
11 | var core_1 = require("@babel/core");
|
12 | var types_1 = require("@babel/types");
|
13 | var schema_utils_1 = require("schema-utils");
|
14 | var OPTIONS_SCHEMA = tslib_1.__importStar(require("./options.schema.json"));
|
15 | var ts_transformer_1 = require("@formatjs/ts-transformer");
|
16 | var DEFAULT_COMPONENT_NAMES = ['FormattedMessage'];
|
17 | var EXTRACTED = Symbol('ReactIntlExtracted');
|
18 | var DESCRIPTOR_PROPS = new Set(['id', 'description', 'defaultMessage']);
|
19 | function getICUMessageValue(messagePath, _a) {
|
20 | var _b = _a === void 0 ? {} : _a, _c = _b.isJSXSource, isJSXSource = _c === void 0 ? false : _c;
|
21 | if (!messagePath) {
|
22 | return '';
|
23 | }
|
24 | var message = getMessageDescriptorValue(messagePath)
|
25 | .trim()
|
26 | .replace(/\s+/gm, ' ');
|
27 | try {
|
28 | intl_messageformat_parser_1.parse(message);
|
29 | }
|
30 | catch (parseError) {
|
31 | if (isJSXSource &&
|
32 | messagePath.isLiteral() &&
|
33 | message.indexOf('\\\\') >= 0) {
|
34 | throw messagePath.buildCodeFrameError('[React Intl] Message failed to parse. ' +
|
35 | 'It looks like `\\`s were used for escaping, ' +
|
36 | "this won't work with JSX string literals. " +
|
37 | 'Wrap with `{}`. ' +
|
38 | 'See: http://facebook.github.io/react/docs/jsx-gotchas.html');
|
39 | }
|
40 | throw messagePath.buildCodeFrameError('[React Intl] Message failed to parse. ' +
|
41 | 'See: https://formatjs.io/docs/core-concepts/icu-syntax' +
|
42 | ("\n" + parseError));
|
43 | }
|
44 | return message;
|
45 | }
|
46 | function evaluatePath(path) {
|
47 | var evaluated = path.evaluate();
|
48 | if (evaluated.confident) {
|
49 | return evaluated.value;
|
50 | }
|
51 | throw path.buildCodeFrameError('[React Intl] Messages must be statically evaluate-able for extraction.');
|
52 | }
|
53 | function getMessageDescriptorKey(path) {
|
54 | if (path.isIdentifier() || path.isJSXIdentifier()) {
|
55 | return path.node.name;
|
56 | }
|
57 | return evaluatePath(path);
|
58 | }
|
59 | function getMessageDescriptorValue(path) {
|
60 | if (!path) {
|
61 | return '';
|
62 | }
|
63 | if (path.isJSXExpressionContainer()) {
|
64 | path = path.get('expression');
|
65 | }
|
66 |
|
67 | var descriptorValue = evaluatePath(path);
|
68 | return descriptorValue;
|
69 | }
|
70 | function createMessageDescriptor(propPaths) {
|
71 | return propPaths.reduce(function (hash, _a) {
|
72 | var keyPath = _a[0], valuePath = _a[1];
|
73 | var key = getMessageDescriptorKey(keyPath);
|
74 | if (DESCRIPTOR_PROPS.has(key)) {
|
75 | hash[key] = valuePath;
|
76 | }
|
77 | return hash;
|
78 | }, {
|
79 | id: undefined,
|
80 | defaultMessage: undefined,
|
81 | description: undefined,
|
82 | });
|
83 | }
|
84 | function evaluateMessageDescriptor(descriptorPath, isJSXSource, filename, idInterpolationPattern, overrideIdFn) {
|
85 | if (isJSXSource === void 0) { isJSXSource = false; }
|
86 | if (idInterpolationPattern === void 0) { idInterpolationPattern = '[contenthash:5]'; }
|
87 | var id = getMessageDescriptorValue(descriptorPath.id);
|
88 | var defaultMessage = getICUMessageValue(descriptorPath.defaultMessage, {
|
89 | isJSXSource: isJSXSource,
|
90 | });
|
91 | var description = getMessageDescriptorValue(descriptorPath.description);
|
92 | if (overrideIdFn) {
|
93 | id = overrideIdFn(id, defaultMessage, description, filename);
|
94 | }
|
95 | else if (!id && idInterpolationPattern && defaultMessage) {
|
96 | id = ts_transformer_1.interpolateName({ resourcePath: filename }, idInterpolationPattern, {
|
97 | content: description
|
98 | ? defaultMessage + "#" + description
|
99 | : defaultMessage,
|
100 | });
|
101 | }
|
102 | var descriptor = {
|
103 | id: id,
|
104 | };
|
105 | if (description) {
|
106 | descriptor.description = description;
|
107 | }
|
108 | if (defaultMessage) {
|
109 | descriptor.defaultMessage = defaultMessage;
|
110 | }
|
111 | return descriptor;
|
112 | }
|
113 | function storeMessage(_a, path, _b, filename, messages) {
|
114 | var id = _a.id, description = _a.description, defaultMessage = _a.defaultMessage;
|
115 | var extractSourceLocation = _b.extractSourceLocation;
|
116 | if (!id && !defaultMessage) {
|
117 | throw path.buildCodeFrameError('[React Intl] Message Descriptors require an `id` or `defaultMessage`.');
|
118 | }
|
119 | if (messages.has(id)) {
|
120 | var existing = messages.get(id);
|
121 | if (description !== existing.description ||
|
122 | defaultMessage !== existing.defaultMessage) {
|
123 | throw path.buildCodeFrameError("[React Intl] Duplicate message id: \"" + id + "\", " +
|
124 | 'but the `description` and/or `defaultMessage` are different.');
|
125 | }
|
126 | }
|
127 | var loc = {};
|
128 | if (extractSourceLocation) {
|
129 | loc = tslib_1.__assign({ file: filename }, path.node.loc);
|
130 | }
|
131 | messages.set(id, tslib_1.__assign({ id: id, description: description, defaultMessage: defaultMessage }, loc));
|
132 | }
|
133 | function referencesImport(path, mod, importedNames) {
|
134 | if (!(path.isIdentifier() || path.isJSXIdentifier())) {
|
135 | return false;
|
136 | }
|
137 | return importedNames.some(function (name) { return path.referencesImport(mod, name); });
|
138 | }
|
139 | function isFormatMessageDestructuring(scope) {
|
140 | var binding = scope.getBinding('formatMessage');
|
141 | var block = scope.block;
|
142 | var declNode = binding === null || binding === void 0 ? void 0 : binding.path.node;
|
143 |
|
144 | if (core_1.types.isVariableDeclarator(declNode)) {
|
145 |
|
146 | if (core_1.types.isCallExpression(declNode.init)) {
|
147 | if (core_1.types.isIdentifier(declNode.init.callee)) {
|
148 | return declNode.init.callee.name === 'useIntl';
|
149 | }
|
150 | }
|
151 | return (core_1.types.isObjectPattern(declNode.id) &&
|
152 | declNode.id.properties.find(function (value) { return value.key.name === 'intl'; }));
|
153 | }
|
154 |
|
155 | if (core_1.types.isFunctionDeclaration(block) &&
|
156 | block.params.length &&
|
157 | core_1.types.isObjectPattern(block.params[0])) {
|
158 | return block.params[0].properties.find(function (value) { return value.key.name === 'intl'; });
|
159 | }
|
160 | return false;
|
161 | }
|
162 | function isFormatMessageCall(callee, path) {
|
163 | if (callee.isIdentifier() &&
|
164 | callee.node.name === 'formatMessage' &&
|
165 | isFormatMessageDestructuring(path.scope)) {
|
166 | return true;
|
167 | }
|
168 | if (!callee.isMemberExpression()) {
|
169 | return false;
|
170 | }
|
171 | var object = callee.get('object');
|
172 | var property = callee.get('property');
|
173 | return (property.isIdentifier() &&
|
174 | property.node.name === 'formatMessage' &&
|
175 | !Array.isArray(object) &&
|
176 |
|
177 | ((object.isIdentifier() && object.node.name === 'intl') ||
|
178 |
|
179 | (object.isMemberExpression() &&
|
180 | object.get('property').node.name === 'intl')));
|
181 | }
|
182 | function assertObjectExpression(path, callee) {
|
183 | if (!path || !path.isObjectExpression()) {
|
184 | throw path.buildCodeFrameError("[React Intl] `" + callee.get('property').node.name + "()` must be " +
|
185 | 'called with an object expression with values ' +
|
186 | 'that are React Intl Message Descriptors, also ' +
|
187 | 'defined as object expressions.');
|
188 | }
|
189 | return true;
|
190 | }
|
191 | exports.default = helper_plugin_utils_1.declare(function (api, options) {
|
192 | api.assertVersion(7);
|
193 | schema_utils_1.validate(OPTIONS_SCHEMA, options, {
|
194 | name: 'babel-plugin-react-intl',
|
195 | baseDataPath: 'options',
|
196 | });
|
197 | var pragma = options.pragma;
|
198 | |
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | function tagAsExtracted(path) {
|
206 | path.node[EXTRACTED] = true;
|
207 | }
|
208 | function wasExtracted(path) {
|
209 | return !!path.node[EXTRACTED];
|
210 | }
|
211 | return {
|
212 | pre: function () {
|
213 | if (!this.ReactIntlMessages) {
|
214 | this.ReactIntlMessages = new Map();
|
215 | this.ReactIntlMeta = {};
|
216 | }
|
217 | },
|
218 | post: function (state) {
|
219 | var _a = this, messages = _a.ReactIntlMessages, ReactIntlMeta = _a.ReactIntlMeta;
|
220 | var descriptors = Array.from(messages.values());
|
221 | state.metadata['react-intl'] = {
|
222 | messages: descriptors,
|
223 | meta: ReactIntlMeta,
|
224 | };
|
225 | },
|
226 | visitor: {
|
227 | Program: function (path) {
|
228 | var body = path.node.body;
|
229 | var ReactIntlMeta = this.ReactIntlMeta;
|
230 | if (!pragma) {
|
231 | return;
|
232 | }
|
233 | for (var _i = 0, body_1 = body; _i < body_1.length; _i++) {
|
234 | var leadingComments = body_1[_i].leadingComments;
|
235 | if (!leadingComments) {
|
236 | continue;
|
237 | }
|
238 | var pragmaLineNode = leadingComments.find(function (c) {
|
239 | return c.value.includes(pragma);
|
240 | });
|
241 | if (!pragmaLineNode) {
|
242 | continue;
|
243 | }
|
244 | pragmaLineNode.value
|
245 | .split(pragma)[1]
|
246 | .trim()
|
247 | .split(/\s+/g)
|
248 | .forEach(function (kv) {
|
249 | var _a = kv.split(':'), k = _a[0], v = _a[1];
|
250 | ReactIntlMeta[k] = v;
|
251 | });
|
252 | }
|
253 | },
|
254 | JSXOpeningElement: function (path, _a) {
|
255 | var opts = _a.opts, filename = _a.file.opts.filename;
|
256 | var _b = opts.moduleSourceName, moduleSourceName = _b === void 0 ? 'react-intl' : _b, _c = opts.additionalComponentNames, additionalComponentNames = _c === void 0 ? [] : _c, removeDefaultMessage = opts.removeDefaultMessage, idInterpolationPattern = opts.idInterpolationPattern, overrideIdFn = opts.overrideIdFn, ast = opts.ast;
|
257 | if (wasExtracted(path)) {
|
258 | return;
|
259 | }
|
260 | var name = path.get('name');
|
261 | if (name.referencesImport(moduleSourceName, 'FormattedPlural')) {
|
262 | if (path.node && path.node.loc)
|
263 | console.warn("[React Intl] Line " + path.node.loc.start.line + ": " +
|
264 | 'Default messages are not extracted from ' +
|
265 | '<FormattedPlural>, use <FormattedMessage> instead.');
|
266 | return;
|
267 | }
|
268 | if (name.isJSXIdentifier() &&
|
269 | (referencesImport(name, moduleSourceName, DEFAULT_COMPONENT_NAMES) ||
|
270 | additionalComponentNames.includes(name.node.name))) {
|
271 | var attributes = path
|
272 | .get('attributes')
|
273 | .filter(function (attr) { return attr.isJSXAttribute(); });
|
274 | var descriptorPath = createMessageDescriptor(attributes.map(function (attr) { return [
|
275 | attr.get('name'),
|
276 | attr.get('value'),
|
277 | ]; }));
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 | if (descriptorPath.id || descriptorPath.defaultMessage) {
|
285 |
|
286 |
|
287 | var descriptor = evaluateMessageDescriptor(descriptorPath, true, filename, idInterpolationPattern, overrideIdFn);
|
288 | storeMessage(descriptor, path, opts, filename, this.ReactIntlMessages);
|
289 | var idAttr = void 0;
|
290 | var descriptionAttr = void 0;
|
291 | var defaultMessageAttr = void 0;
|
292 | for (var _i = 0, attributes_1 = attributes; _i < attributes_1.length; _i++) {
|
293 | var attr = attributes_1[_i];
|
294 | if (!attr.isJSXAttribute()) {
|
295 | continue;
|
296 | }
|
297 | switch (getMessageDescriptorKey(attr.get('name'))) {
|
298 | case 'description':
|
299 | descriptionAttr = attr;
|
300 | break;
|
301 | case 'defaultMessage':
|
302 | defaultMessageAttr = attr;
|
303 | break;
|
304 | case 'id':
|
305 | idAttr = attr;
|
306 | break;
|
307 | }
|
308 | }
|
309 | if (descriptionAttr) {
|
310 | descriptionAttr.remove();
|
311 | }
|
312 | if (!removeDefaultMessage &&
|
313 | ast &&
|
314 | descriptor.defaultMessage &&
|
315 | defaultMessageAttr) {
|
316 | defaultMessageAttr
|
317 | .get('value')
|
318 | .replaceWith(core_1.types.jsxExpressionContainer(core_1.types.stringLiteral('foo')));
|
319 | defaultMessageAttr.get('value')
|
320 | .get('expression')
|
321 | .replaceWithSourceString(JSON.stringify(intl_messageformat_parser_1.parse(descriptor.defaultMessage)));
|
322 | }
|
323 | if (overrideIdFn || (descriptor.id && idInterpolationPattern)) {
|
324 | if (idAttr) {
|
325 | idAttr.get('value').replaceWith(core_1.types.stringLiteral(descriptor.id));
|
326 | }
|
327 | else if (defaultMessageAttr) {
|
328 | defaultMessageAttr.insertBefore(core_1.types.jsxAttribute(core_1.types.jsxIdentifier('id'), core_1.types.stringLiteral(descriptor.id)));
|
329 | }
|
330 | }
|
331 | if (removeDefaultMessage && defaultMessageAttr) {
|
332 | defaultMessageAttr.remove();
|
333 | }
|
334 |
|
335 | tagAsExtracted(path);
|
336 | }
|
337 | }
|
338 | },
|
339 | CallExpression: function (path, _a) {
|
340 | var opts = _a.opts, filename = _a.file.opts.filename;
|
341 | var messages = this.ReactIntlMessages;
|
342 | var _b = opts.moduleSourceName, moduleSourceName = _b === void 0 ? 'react-intl' : _b, overrideIdFn = opts.overrideIdFn, idInterpolationPattern = opts.idInterpolationPattern, removeDefaultMessage = opts.removeDefaultMessage, extractFromFormatMessageCall = opts.extractFromFormatMessageCall, ast = opts.ast;
|
343 | var callee = path.get('callee');
|
344 | |
345 |
|
346 |
|
347 |
|
348 | function processMessageObject(messageDescriptor) {
|
349 | assertObjectExpression(messageDescriptor, callee);
|
350 | if (wasExtracted(messageDescriptor)) {
|
351 | return;
|
352 | }
|
353 | var properties = messageDescriptor.get('properties');
|
354 | var descriptorPath = createMessageDescriptor(properties.map(function (prop) {
|
355 | return [prop.get('key'), prop.get('value')];
|
356 | }));
|
357 |
|
358 | var descriptor = evaluateMessageDescriptor(descriptorPath, false, filename, idInterpolationPattern, overrideIdFn);
|
359 | storeMessage(descriptor, messageDescriptor, opts, filename, messages);
|
360 |
|
361 | messageDescriptor.replaceWithSourceString(JSON.stringify(tslib_1.__assign({ id: descriptor.id }, (!removeDefaultMessage && descriptor.defaultMessage
|
362 | ? {
|
363 | defaultMessage: ast
|
364 | ? intl_messageformat_parser_1.parse(descriptor.defaultMessage)
|
365 | : descriptor.defaultMessage,
|
366 | }
|
367 | : {}))));
|
368 |
|
369 | tagAsExtracted(messageDescriptor);
|
370 | }
|
371 |
|
372 | if (isMultipleMessagesDeclMacro(callee, moduleSourceName) ||
|
373 | isSingularMessagesDeclMacro(callee, moduleSourceName)) {
|
374 | var firstArgument = path.get('arguments')[0];
|
375 | var messagesObj = getMessagesObjectFromExpression(firstArgument);
|
376 | if (assertObjectExpression(messagesObj, callee)) {
|
377 | if (isSingularMessagesDeclMacro(callee, moduleSourceName)) {
|
378 | processMessageObject(messagesObj);
|
379 | }
|
380 | else {
|
381 | var properties = messagesObj.get('properties');
|
382 | if (Array.isArray(properties)) {
|
383 | properties
|
384 | .map(function (prop) { return prop.get('value'); })
|
385 | .forEach(processMessageObject);
|
386 | }
|
387 | }
|
388 | }
|
389 | }
|
390 |
|
391 | if (extractFromFormatMessageCall && isFormatMessageCall(callee, path)) {
|
392 | var messageDescriptor = path.get('arguments')[0];
|
393 | if (messageDescriptor.isObjectExpression()) {
|
394 | processMessageObject(messageDescriptor);
|
395 | }
|
396 | }
|
397 | },
|
398 | },
|
399 | };
|
400 | });
|
401 | function isMultipleMessagesDeclMacro(callee, moduleSourceName) {
|
402 | return referencesImport(callee, moduleSourceName, ['defineMessages']);
|
403 | }
|
404 | function isSingularMessagesDeclMacro(callee, moduleSourceName) {
|
405 | return referencesImport(callee, moduleSourceName, ['defineMessage']);
|
406 | }
|
407 | function getMessagesObjectFromExpression(nodePath) {
|
408 | var currentPath = nodePath;
|
409 | while (types_1.isTSAsExpression(currentPath.node) ||
|
410 | types_1.isTSTypeAssertion(currentPath.node) ||
|
411 | types_1.isTypeCastExpression(currentPath.node)) {
|
412 | currentPath = currentPath.get('expression');
|
413 | }
|
414 | return currentPath;
|
415 | }
|