@robotical/martyblocks
Version:
MartyBlocks based on Scratch for Marty the Robot by Robotical
328 lines • 15.6 kB
JavaScript
/*
* 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