/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * In order to speed things up, we use transform caching. However, the project * codebase moves very fast and cache becomes invalid when some files change. * * CacheKey is used to generate a unique string that can be used as cache key. * The cache key consists of various dependency cache keys that change if the * output of the transform would change. * * Imported from https://github.com/gaearon/babel-plugin-react-transform * https://github.com/gaearon/babel-plugin-react-transform/commit/d7069df5c6f36a07857fd108067dc515b3a795ee * * @format * @noflow */ "use strict"; // temporary workaround, don't mind this package /*eslint-disable eslint-comments/no-unlimited-disable*/ /*eslint-disable*/ const _require = require("@babel/helper-module-imports"), addDefault = _require.addDefault; const _require2 = require("path"), relative = _require2.relative; function find(obj, func) { let value = undefined; if (!(obj instanceof Array)) { obj = Object.values(obj); } obj.some((v, i) => { if (func(v)) { value = v; return true; } return false; }); return value; } module.exports = function(_ref) { let t = _ref.types, template = _ref.template; function matchesPatterns(path, patterns) { return !!find(patterns, pattern => { return ( t.isIdentifier(path.node, { name: pattern }) || path.matchesPattern(pattern) ); }); } function isReactLikeClass(node) { return !!find(node.body.body, classMember => { return ( t.isClassMethod(classMember) && t.isIdentifier(classMember.key, { name: "render" }) ); }); } function isReactLikeComponentObject(node) { return ( t.isObjectExpression(node) && !!find(node.properties, objectMember => { return ( (t.isObjectProperty(objectMember) || t.isObjectMethod(objectMember)) && (t.isIdentifier(objectMember.key, { name: "render" }) || t.isStringLiteral(objectMember.key, { value: "render" })) ); }) ); } // `foo({ displayName: 'NAME' });` => 'NAME' function getDisplayName(node) { const property = find( node.arguments[0].properties, _node => _node.key.name === "displayName" ); return property && property.value.value; } function hasParentFunction(path) { return !!path.findParent(parentPath => parentPath.isFunction()); } // wrapperFunction("componentId")(node) function wrapComponent(node, componentId, wrapperFunctionId) { return t.callExpression( t.callExpression(wrapperFunctionId, [t.stringLiteral(componentId)]), [node] ); } // `{ name: foo }` => Node { type: "ObjectExpression", properties: [...] } function toObjectExpression(object) { const properties = Object.keys(object).map(key => { return t.objectProperty(t.identifier(key), object[key]); }); return t.objectExpression(properties); } const wrapperFunctionTemplate = template(` function WRAPPER_FUNCTION_ID(ID_PARAM) { return function(COMPONENT_PARAM) { return EXPRESSION; }; } `); const VISITED_KEY = "react-transform-" + Date.now(); const componentVisitor = { Class(path) { if ( path.node[VISITED_KEY] || !matchesPatterns(path.get("superClass"), this.superClasses) || !isReactLikeClass(path.node) ) { return; } path.node[VISITED_KEY] = true; const componentName = (path.node.id && path.node.id.name) || null; const componentId = componentName || path.scope.generateUid("component"); const isInFunction = hasParentFunction(path); this.components.push({ id: componentId, name: componentName, isInFunction: isInFunction }); // Can't wrap ClassDeclarations const isStatement = t.isStatement(path.node); const isExport = t.isExportDefaultDeclaration(path.parent); if (isStatement && !isExport) { // class decl // need to work around Babel 7 detecting duplicate decls here path.insertAfter( t.expressionStatement( t.assignmentExpression( "=", t.identifier(componentId), wrapComponent( t.identifier(componentId), componentId, this.wrapperFunctionId ) ) ) ); return; } const expression = t.toExpression(path.node); // wrapperFunction("componentId")(node) let wrapped = wrapComponent( expression, componentId, this.wrapperFunctionId ); let constId; if (isStatement) { // wrapperFunction("componentId")(class Foo ...) => const Foo = wrapperFunction("componentId")(class Foo ...) constId = t.identifier(componentName || componentId); wrapped = t.variableDeclaration("const", [ t.variableDeclarator(constId, wrapped) ]); } if (isExport) { path.parentPath.insertBefore(wrapped); path.parent.declaration = constId; } else { path.replaceWith(wrapped); } }, CallExpression(path) { if ( path.node[VISITED_KEY] || !matchesPatterns(path.get("callee"), this.factoryMethods) || !isReactLikeComponentObject(path.node.arguments[0]) ) { return; } path.node[VISITED_KEY] = true; // `foo({ displayName: 'NAME' });` => 'NAME' const componentName = getDisplayName(path.node); const componentId = componentName || path.scope.generateUid("component"); const isInFunction = hasParentFunction(path); this.components.push({ id: componentId, name: componentName, isInFunction: isInFunction }); path.replaceWith( wrapComponent(path.node, componentId, this.wrapperFunctionId) ); } }; class ReactTransformBuilder { constructor(file, options) { this.file = file; this.program = file.path; this.options = this.normalizeOptions(options); // @todo: clean this shit up this.configuredTransformsIds = []; } static validateOptions(options) { return typeof options === "object" && Array.isArray(options.transforms); } static assertValidOptions(options) { if (!ReactTransformBuilder.validateOptions(options)) { throw new Error( "babel-plugin-react-transform requires that you specify options " + "in .babelrc or from the Babel Node API, and that it is an object " + "with a transforms property which is an array." ); } } normalizeOptions(options) { return { factoryMethods: options.factoryMethods || ["React.createClass"], superClasses: options.superClasses || [ "React.Component", "React.PureComponent", "Component", "PureComponent" ], transforms: options.transforms.map(opts => { return { transform: opts.transform, locals: opts.locals || [], imports: opts.imports || [] }; }) }; } build(path) { const componentsDeclarationId = this.file.scope.generateUidIdentifier( "components" ); const wrapperFunctionId = this.file.scope.generateUidIdentifier( "wrapComponent" ); const components = this.collectAndWrapComponents(wrapperFunctionId); if (!components.length) { return; } const componentsDeclaration = this.initComponentsDeclaration( componentsDeclarationId, components ); const configuredTransforms = this.initTransformers( path, componentsDeclarationId ); const wrapperFunction = this.initWrapperFunction(wrapperFunctionId); const body = this.program.node.body; body.unshift(wrapperFunction); configuredTransforms.reverse().forEach(node => body.unshift(node)); body.unshift(componentsDeclaration); } /** * const Foo = _wrapComponent('Foo')(class Foo extends React.Component {}); * ... * const Bar = _wrapComponent('Bar')(React.createClass({ * displayName: 'Bar' * })); */ collectAndWrapComponents(wrapperFunctionId) { const components = []; this.file.path.traverse(componentVisitor, { wrapperFunctionId: wrapperFunctionId, components: components, factoryMethods: this.options.factoryMethods, superClasses: this.options.superClasses, currentlyInFunction: false }); return components; } /** * const _components = { * Foo: { * displayName: "Foo" * } * }; */ initComponentsDeclaration(componentsDeclarationId, components) { const props = components.map(component => { const componentId = component.id; const componentProps = []; if (component.name) { componentProps.push( t.objectProperty( t.identifier("displayName"), t.stringLiteral(component.name) ) ); } if (component.isInFunction) { componentProps.push( t.objectProperty( t.identifier("isInFunction"), t.booleanLiteral(true) ) ); } let objectKey; if (t.isValidIdentifier(componentId)) { objectKey = t.identifier(componentId); } else { objectKey = t.stringLiteral(componentId); } return t.objectProperty(objectKey, t.objectExpression(componentProps)); }); return t.variableDeclaration("const", [ t.variableDeclarator(componentsDeclarationId, t.objectExpression(props)) ]); } /** * import _transformLib from "transform-lib"; * ... * const _transformLib2 = _transformLib({ * filename: "filename", * components: _components, * locals: [], * imports: [] * }); */ initTransformers(path, componentsDeclarationId) { return this.options.transforms.map(transform => { const transformName = transform.transform; const transformImportId = addDefault(path, transformName, { nameHint: transformName }); const transformLocals = transform.locals.map(local => { return t.identifier(local); }); const transformImports = transform.imports.map(importName => { return addDefault(path, importName, { hint: importName }); }); const configuredTransformId = this.file.scope.generateUidIdentifier( transformName ); const configuredTransform = t.variableDeclaration("const", [ t.variableDeclarator( configuredTransformId, t.callExpression(transformImportId, [ toObjectExpression({ filename: t.stringLiteral( // Equivalent to `this.file.opts.sourceFileName` when defined. this.file.opts.filename ? relative(this.file.opts.cwd, this.file.opts.filename) : "unknown" ), components: componentsDeclarationId, locals: t.arrayExpression(transformLocals), imports: t.arrayExpression(transformImports) }) ]) ) ]); this.configuredTransformsIds.push(configuredTransformId); return configuredTransform; }); } /** * function _wrapComponent(id) { * return function (Component) { * return _transformLib2(Component, id); * }; * } */ initWrapperFunction(wrapperFunctionId) { const idParam = t.identifier("id"); const componentParam = t.identifier("Component"); const expression = this.configuredTransformsIds .reverse() .reduce((memo, transformId) => { return t.callExpression(transformId, [memo, idParam]); }, componentParam); return wrapperFunctionTemplate({ WRAPPER_FUNCTION_ID: wrapperFunctionId, ID_PARAM: idParam, COMPONENT_PARAM: componentParam, EXPRESSION: expression }); } } return { visitor: { Program(path, _ref2) { let file = _ref2.file, opts = _ref2.opts; ReactTransformBuilder.assertValidOptions(opts); const builder = new ReactTransformBuilder(file, opts); builder.build(path); } } }; };