UNPKG

@robotical/martyblocks

Version:
328 lines 15.6 kB
/* * Copyright 2015, Yahoo Inc. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; import * as p from 'path'; import { writeFileSync } from 'fs'; import { mkdirpSync } from 'fs-extra'; import printICUMessage from './print-icu-message'; var declare = require('@babel/helper-plugin-utils').declare; import { types as t } from '@babel/core'; var DEFAULT_COMPONENT_NAMES = ['FormattedMessage', 'FormattedHTMLMessage']; var FUNCTION_NAMES = ['defineMessages']; var EXTRACTED = Symbol('ReactIntlExtracted'); var DESCRIPTOR_PROPS = new Set(['id', 'description', 'defaultMessage']); function getICUMessageValue(messagePath, _a) { var _b = (_a === void 0 ? {} : _a).isJSXSource, isJSXSource = _b === void 0 ? false : _b; if (!messagePath) { return ''; } var message = getMessageDescriptorValue(messagePath); try { return printICUMessage(message); } catch (parseError) { if (isJSXSource && messagePath.isLiteral() && message.indexOf('\\\\') >= 0) { throw messagePath.buildCodeFrameError('[React Intl] Message failed to parse. ' + 'It looks like `\\`s were used for escaping, ' + "this won't work with JSX string literals. " + 'Wrap with `{}`. ' + 'See: http://facebook.github.io/react/docs/jsx-gotchas.html'); } throw messagePath.buildCodeFrameError('[React Intl] Message failed to parse. ' + 'See: http://formatjs.io/guides/message-syntax/' + ("\n" + parseError)); } } function evaluatePath(path) { var evaluated = path.evaluate(); if (evaluated.confident) { return evaluated.value; } throw path.buildCodeFrameError('[React Intl] Messages must be statically evaluate-able for extraction.'); } function getMessageDescriptorKey(path) { if (path.isIdentifier() || path.isJSXIdentifier()) { return path.node.name; } return evaluatePath(path); } function getMessageDescriptorValue(path) { if (!path) { return ''; } if (path.isJSXExpressionContainer()) { path = path.get('expression'); } // Always trim the Message Descriptor values. var descriptorValue = evaluatePath(path); if (typeof descriptorValue === 'string') { return descriptorValue.trim(); } return descriptorValue; } function createMessageDescriptor(propPaths) { return propPaths.reduce(function (hash, _a) { var keyPath = _a[0], valuePath = _a[1]; var key = getMessageDescriptorKey(keyPath); if (DESCRIPTOR_PROPS.has(key)) { hash[key] = valuePath; } return hash; }, { id: undefined, defaultMessage: undefined, description: undefined }); } function evaluateMessageDescriptor(descriptorPath, isJSXSource, overrideIdFn) { if (isJSXSource === void 0) { isJSXSource = false; } var id = getMessageDescriptorValue(descriptorPath.id); var defaultMessage = getICUMessageValue(descriptorPath.defaultMessage, { isJSXSource: isJSXSource }); var description = getMessageDescriptorValue(descriptorPath.description); if (overrideIdFn) { id = overrideIdFn(id, defaultMessage, description); } var descriptor = { id: id }; if (description) { descriptor.description = description; } if (defaultMessage) { descriptor.defaultMessage = defaultMessage; } return descriptor; } function storeMessage(_a, path, _b, filename, messages) { var id = _a.id, description = _a.description, defaultMessage = _a.defaultMessage; var enforceDescriptions = _b.enforceDescriptions, _c = _b.enforceDefaultMessage, enforceDefaultMessage = _c === void 0 ? true : _c, extractSourceLocation = _b.extractSourceLocation; if (!id || (enforceDefaultMessage && !defaultMessage)) { throw path.buildCodeFrameError('[React Intl] Message Descriptors require an `id` and `defaultMessage`.'); } if (messages.has(id)) { var existing = messages.get(id); if (description !== existing.description || defaultMessage !== existing.defaultMessage) { throw path.buildCodeFrameError("[React Intl] Duplicate message id: \"" + id + "\", " + 'but the `description` and/or `defaultMessage` are different.'); } } if (enforceDescriptions) { if (!description || (typeof description === 'object' && Object.keys(description).length < 1)) { throw path.buildCodeFrameError('[React Intl] Message must have a `description`.'); } } var loc = {}; if (extractSourceLocation) { loc = __assign({ file: p.relative(process.cwd(), filename) }, path.node.loc); } messages.set(id, __assign({ id: id, description: description, defaultMessage: defaultMessage }, loc)); } function referencesImport(path, mod, importedNames) { if (!(path.isIdentifier() || path.isJSXIdentifier())) { return false; } return importedNames.some(function (name) { return path.referencesImport(mod, name); }); } function isFormatMessageCall(callee) { if (!callee.isMemberExpression()) { return false; } var object = callee.get('object'); var property = callee.get('property'); return (property.isIdentifier() && property.node.name === 'formatMessage' && // things like `intl.formatMessage` ((object.isIdentifier() && object.node.name === 'intl') || // things like `this.props.intl.formatMessage` (object.isMemberExpression() && object.get('property').node.name === 'intl'))); } function assertObjectExpression(path, callee) { if (!path || !path.isObjectExpression()) { throw path.buildCodeFrameError("[React Intl] `" + callee.get('property').node.name + "()` must be " + 'called with an object expression with values ' + 'that are React Intl Message Descriptors, also ' + 'defined as object expressions.'); } return true; } export default declare(function (api) { api.assertVersion(7); /** * Store this in the node itself so that multiple passes work. Specifically * if we remove `description` in the 1st pass, 2nd pass will fail since * it expect `description` to be there. * HACK: We store this in the node instance since this persists across * multiple plugin runs */ function tagAsExtracted(path) { path.node[EXTRACTED] = true; } function wasExtracted(path) { return !!path.node[EXTRACTED]; } return { pre: function () { if (!this.ReactIntlMessages) { this.ReactIntlMessages = new Map(); } }, post: function (state) { var _a = this, filename = _a.file.opts.filename, messagesDir = _a.opts.messagesDir; var basename = p.basename(filename, p.extname(filename)); var messages = this.ReactIntlMessages; var descriptors = Array.from(messages.values()); state.metadata['react-intl'] = { messages: descriptors }; if (messagesDir && descriptors.length > 0) { // Make sure the relative path is "absolute" before // joining it with the `messagesDir`. var relativePath = p.join(p.sep, p.relative(process.cwd(), filename)); // Solve when the window user has symlink on the directory, because // process.cwd on windows returns the symlink root, // and filename (from babel) returns the original root if (process.platform === 'win32') { var name_1 = p.parse(process.cwd()).name; if (relativePath.includes(name_1)) { relativePath = relativePath.slice(relativePath.indexOf(name_1) + name_1.length); } } var messagesFilename = p.join(messagesDir, p.dirname(relativePath), basename + '.json'); var messagesFile = JSON.stringify(descriptors, null, 2); mkdirpSync(p.dirname(messagesFilename)); writeFileSync(messagesFilename, messagesFile); } }, visitor: { JSXOpeningElement: function (path, _a) { var opts = _a.opts, filename = _a.file.opts.filename; var _b = opts.moduleSourceName, moduleSourceName = _b === void 0 ? 'react-intl' : _b, _c = opts.additionalComponentNames, additionalComponentNames = _c === void 0 ? [] : _c, enforceDefaultMessage = opts.enforceDefaultMessage, removeDefaultMessage = opts.removeDefaultMessage, overrideIdFn = opts.overrideIdFn; if (wasExtracted(path)) { return; } var name = path.get('name'); if (name.referencesImport(moduleSourceName, 'FormattedPlural')) { if (path.node && path.node.loc) console.warn("[React Intl] Line " + path.node.loc.start.line + ": " + 'Default messages are not extracted from ' + '<FormattedPlural>, use <FormattedMessage> instead.'); return; } if (name.isJSXIdentifier() && (referencesImport(name, moduleSourceName, DEFAULT_COMPONENT_NAMES) || additionalComponentNames.includes(name.node.name))) { var attributes = path .get('attributes') .filter(function (attr) { return attr.isJSXAttribute(); }); var descriptorPath = createMessageDescriptor(attributes.map(function (attr) { return [ attr.get('name'), attr.get('value') ]; })); // In order for a default message to be extracted when // declaring a JSX element, it must be done with standard // `key=value` attributes. But it's completely valid to // write `<FormattedMessage {...descriptor} />` or // `<FormattedMessage id={dynamicId} />`, because it will be // skipped here and extracted elsewhere. The descriptor will // be extracted only if a `defaultMessage` prop exists and // `enforceDefaultMessage` is `true`. if (enforceDefaultMessage === false || descriptorPath.defaultMessage) { // Evaluate the Message Descriptor values in a JSX // context, then store it. var descriptor_1 = evaluateMessageDescriptor(descriptorPath, true, overrideIdFn); storeMessage(descriptor_1, path, opts, filename, this.ReactIntlMessages); attributes.forEach(function (attr) { var ketPath = attr.get('name'); var msgDescriptorKey = getMessageDescriptorKey(ketPath); if ( // Remove description since it's not used at runtime. msgDescriptorKey === 'description' || // Remove defaultMessage if opts says so. (removeDefaultMessage && msgDescriptorKey === 'defaultMessage')) { attr.remove(); } else if (overrideIdFn && getMessageDescriptorKey(ketPath) === 'id') { attr.get('value').replaceWith(t.stringLiteral(descriptor_1.id)); } }); // Tag the AST node so we don't try to extract it twice. tagAsExtracted(path); } } }, CallExpression: function (path, _a) { var opts = _a.opts, filename = _a.file.opts.filename; var messages = this.ReactIntlMessages; var _b = opts.moduleSourceName, moduleSourceName = _b === void 0 ? 'react-intl' : _b, overrideIdFn = opts.overrideIdFn, removeDefaultMessage = opts.removeDefaultMessage, extractFromFormatMessageCall = opts.extractFromFormatMessageCall; var callee = path.get('callee'); /** * Process MessageDescriptor * @param messageDescriptor Message Descriptor */ function processMessageObject(messageDescriptor) { assertObjectExpression(messageDescriptor, callee); if (wasExtracted(messageDescriptor)) { return; } var properties = messageDescriptor.get('properties'); var descriptorPath = createMessageDescriptor(properties.map(function (prop) { return [prop.get('key'), prop.get('value')]; })); // Evaluate the Message Descriptor values, then store it. var descriptor = evaluateMessageDescriptor(descriptorPath, false, overrideIdFn); storeMessage(descriptor, messageDescriptor, opts, filename, messages); // Remove description since it's not used at runtime. messageDescriptor.replaceWith(t.objectExpression([ t.objectProperty(t.stringLiteral('id'), t.stringLiteral(descriptor.id)) ].concat((!removeDefaultMessage && descriptor.defaultMessage ? [ t.objectProperty(t.stringLiteral('defaultMessage'), t.stringLiteral(descriptor.defaultMessage)) ] : [])))); // Tag the AST node so we don't try to extract it twice. tagAsExtracted(messageDescriptor); } // Check that this is `defineMessages` call if (referencesImport(callee, moduleSourceName, FUNCTION_NAMES)) { var messagesObj = path.get('arguments')[0]; if (assertObjectExpression(messagesObj, callee)) { messagesObj .get('properties') .map(function (prop) { return prop.get('value'); }) .forEach(processMessageObject); } } // Check that this is `intl.formatMessage` call if (extractFromFormatMessageCall && isFormatMessageCall(callee)) { var messageDescriptor = path.get('arguments')[0]; if (messageDescriptor.isObjectExpression()) { processMessageObject(messageDescriptor); } } } } }; }); //# sourceMappingURL=index.js.map