UNPKG

babel-plugin-flow-react-proptypes

Version:
376 lines (318 loc) 16.2 kB
"use strict"; function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } Object.defineProperty(exports, "__esModule", { value: true }); exports.setMakePropTypeImportNode = setMakePropTypeImportNode; exports.makePropTypesAstForPropTypesAssignment = makePropTypesAstForPropTypesAssignment; exports.makePropTypesAstForExport = makePropTypesAstForExport; var _util = require("./util"); var t = _interopRequireWildcard(require("@babel/types")); var _template = _interopRequireDefault(require("@babel/template")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function makePropTypeImportNode() { return t.callExpression(t.identifier('require'), [(0, _util.makeLiteral)('prop-types')]); } function setMakePropTypeImportNode(fn) { makePropTypeImportNode = fn; } var dontSetTemplate = (0, _template["default"])("\n(props, propName, componentName) => {\n if(props[propName] != null) {\n throw new Error(`Invalid prop \\`${propName}\\` of value \\`${props[propName]}\\` passed to \\`${componentName}\\`. Expected undefined or null.`);\n }\n}\n"); var exactTemplate = (0, _template["default"])("\n(values, prop, displayName) => {\n var props = PROPS;\n var extra = [];\n for (var k in values) {\n if (values.hasOwnProperty(k) && !props.hasOwnProperty(k)) {\n extra.push(k);\n }\n }\n if (extra.length > 0) {\n return new Error('Invalid additional prop(s) ' + JSON.stringify(extra));\n }\n}\n", { placeholderPattern: /^PROPS$/ }); var anyTemplate = (0, _template["default"])("\n(props, propName, componentName) => {\n if (!Object.prototype.hasOwnProperty.call(props, propName)) {\n throw new Error(`Prop \\`${propName}\\` has type 'any' or 'mixed', but was not provided to \\`${componentName}\\`. Pass undefined or any other value.`);\n }\n}\n"); var deferTpl = (0, _template["default"])("(function() { return (NODE).apply(this, arguments); })"); /** * Top-level function to generate prop-types AST. * * This will return an expression suitable for assignment to the * propTypes property of an React component. In particular, * the AST returned will always yield an object. Assignment * to Foo.propTypes is possible for objects, not simple * types. * * @param propTypeData Intermediate representation * @returns {*} AST expression always returning an object. */ function makePropTypesAstForPropTypesAssignment(propTypeData) { var node = null; if (propTypeData.type === 'shape-intersect-runtime') { // For top-level usage, e.g. Foo.proptype, return // an expression returning an object. node = makeObjectMergeAstForShapeIntersectRuntime(propTypeData); } else if (propTypeData.type === 'shape') { node = makeObjectAstForShape(propTypeData); } else if (propTypeData.type === 'raw') { node = makeObjectAstForRaw(propTypeData); } return node; } /** * Top-level function to generate prop-types AST. * * This will return an expression suitable for exporting. * * This function is similar to makePropTypesAstForPropTypesAssignment, except * that you may export non-object expressions. * * Any items not handled by makePropTypesAstForPropTypesAssignment will be returned * as an AST invoking the corresponding function from the prop-types package. * * @param propTypeData Intermediate representation * @returns {*} AST for expression resulting in object or function */ function makePropTypesAstForExport(propTypeData) { var ast = makePropTypesAstForPropTypesAssignment(propTypeData); if (ast == null) { // Now we handle isRequired on the import side // https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/113 propTypeData.isRequired = false; ast = makePropType(propTypeData); } return ast; } function makeAnyPropTypeAST() { var importNode = makePropTypeImportNode(); var anyNode = t.memberExpression(importNode, t.identifier('any')); return anyNode; } function makeObjectAstForRaw(propTypeSpec, propTypeObjects) { var propTypeObject = typeof propTypeSpec.value === 'string' ? t.identifier(propTypeSpec.value) : propTypeSpec.value; // This will just be a variable, referencing an import we // generated above. This variable may contain prop-types.any, // which will not work when used in an intersection. var anyNode = makeAnyPropTypeAST(); var testExpression = t.binaryExpression('===', propTypeObject, anyNode); propTypeObject = t.conditionalExpression(testExpression, t.objectExpression([]), t.cloneDeep(propTypeObject)); return propTypeObject; } /** * Generates AST for run-time merges involving either import variables, * local types (shape) and other run-time merges. * * The returned AST is an expression that, when evaluated, returns an * object: * * The expression may look like this: * * TODO: Does the nested case below actually work? * * Object.assign( * {}, * {foo: bar}, * someImportedType === require('prop-types).any ? {} : someImportedType, * {qux: 2}, * Object.assign( * {}, * {nested: 2} * ), * {quz: require('prop-types').shape({foo: bar}), * ); * * This method is mainly useful when objects are actually required, such as when the * type is participating in an intersection or when the result of the intersection is * to be used as the main proptypes, e.g. for Foo.propTypes = {..object}. * * For other uses, the returned object must be wrapped in a shape. See * makeShapeAstForShapeIntersectRuntime(). * * @param propTypeData intermediate representation * @returns {*} */ function makeObjectMergeAstForShapeIntersectRuntime(propTypeData) { var propTypeObjects = []; propTypeData.properties.forEach(function (propTypeSpec) { if (propTypeSpec.type === 'raw') { propTypeObjects.push(makeObjectAstForRaw(propTypeSpec)); } else if (propTypeSpec.type === 'shape') { propTypeObjects.push(makeObjectAstForShape(propTypeSpec)); } else if (propTypeSpec.type === 'shape-intersect-runtime') { // TODO: simplify all of this recursive code? // This returns an object. propTypeObjects.push(makeObjectMergeAstForShapeIntersectRuntime(propTypeSpec)); } }); var runtimeMerge = t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('assign')), [t.objectExpression([])].concat(propTypeObjects)); return runtimeMerge; } /** * Like makeShapeAstForShapeIntersectRuntime, but wraps the props in a shape. * * This is useful for nested uses. * */ function makeShapeAstForShapeIntersectRuntime(propTypeData) { var runtimeMerge = makeObjectMergeAstForShapeIntersectRuntime(propTypeData); return t.callExpression(t.memberExpression(makePropTypeImportNode(), t.identifier('shape')), [runtimeMerge]); } function makeObjectAstForShape(propTypeData) { // TODO: this is almost duplicated with the shape handling below; // but this code does not generate AST for a shape function, // but returns the AST for the object instead. var rootProperties = propTypeData.properties.map(function (_ref) { var key = _ref.key, value = _ref.value, leadingComments = _ref.leadingComments; var node = t.objectProperty(t.stringLiteral(key), makePropType(value)); if (leadingComments) { node.leadingComments = leadingComments; } return node; }); return t.objectExpression(rootProperties); } function makeFunctionCheckAST(variableNode) { return t.binaryExpression('===', t.unaryExpression('typeof', variableNode), t.stringLiteral('function')); } function makeNullCheckAST(variableNode) { return t.binaryExpression('==', variableNode, t.nullLiteral()); } function markNodeAsRequired(node) { return t.memberExpression(node, t.identifier('isRequired')); } function processQualifiedTypeIdentifierIntoMemberExpression(qualifiedTypeIdentifier) { var qualification = qualifiedTypeIdentifier.qualification; var objectAST; if (qualification.type === 'QualifiedTypeIdentifier') { objectAST = processQualifiedTypeIdentifierIntoMemberExpression(qualification); } else if (qualification.type === 'Identifier') { objectAST = t.identifier(qualification.name); } else { throw new Error('Cannot handle type of qualification property:', qualification); } var propertyAST = t.identifier(qualifiedTypeIdentifier.id.name); var memberExpression = t.memberExpression(objectAST, propertyAST); return t.conditionalExpression(makeNullCheckAST(memberExpression), t.objectExpression([]), memberExpression); } /** * Handles all prop types. * * Returns what is expected on the right-hand side of a proptype; that is, * the actual validation function. * * Some special cases exist related to top-level proptypes, where an object is required * instead of a function. This method does not handle these details: it turns the intermediate * representation into something that can be used inside a propType object: * * Foo.propTypes = { * bar: makePropType(intermediateRepresentation1), * baz: makePropType(intermediateRepresentation2), * } * * @param data Intermediate representation of one single proptype * @param isExact ?? * @returns {*} AST for the prop-types validation function */ function makePropType(data, isExact) { var method = data.type; if (method === 'exact') { data.properties.isRequired = data.isRequired; return makePropType(data.properties, true); } var node = makePropTypeImportNode(); var markFullExpressionAsRequired = true; if (method === 'string' || method === 'number' || method === 'bool' || method === 'object' || method === 'array' || method === 'func' || method === 'node') { node = t.memberExpression(t.cloneDeep(node), t.identifier(method)); } else if (method === 'any' || !method) { markFullExpressionAsRequired = false; if (data.isRequired) { node = anyTemplate().expression; } else { node = t.memberExpression(t.cloneDeep(node), t.identifier('any')); } } else if (method === 'raw') { markFullExpressionAsRequired = false; // In 'raw', we handle variables - typically derived from imported types. // These are either - at run-time - objects or functions. Objects are wrapped in a shape; // for functions, we assume that the variable already contains a proptype assertion var variableNode = typeof data.value === 'string' ? t.identifier(data.value) : t.cloneDeep(data.value); var originalVariableNode = variableNode; var shapeNode = t.callExpression(t.memberExpression(makePropTypeImportNode(), t.identifier('shape')), [t.cloneDeep(variableNode)]); if (data.isRequired) { shapeNode = markNodeAsRequired(shapeNode); } if (data.isRequired) { variableNode = t.conditionalExpression(t.memberExpression(t.cloneDeep(variableNode), t.identifier('isRequired')), t.memberExpression(t.cloneDeep(variableNode), t.identifier('isRequired')), t.cloneDeep(variableNode)); } var functionCheckNode = makeFunctionCheckAST(originalVariableNode); node = t.conditionalExpression(functionCheckNode, variableNode, shapeNode); node = deferTpl({ NODE: node }).expression; } else if (method === 'shape') { var shapeObjectProperties = data.properties.map(function (_ref2) { var key = _ref2.key, value = _ref2.value, leadingComments = _ref2.leadingComments; var node = t.objectProperty(t.identifier(key), makePropType(value)); if (leadingComments) { node.leadingComments = leadingComments; } return node; }); if (isExact || data.isExact) { shapeObjectProperties.push(t.objectProperty(t.identifier('__exact__'), exactTemplate({ 'PROPS': t.objectExpression(data.properties.map(function (_ref3) { var key = _ref3.key; return t.objectProperty(t.stringLiteral(key), t.booleanLiteral(true)); })) }).expression)); } var shapeObjectExpression = t.objectExpression(shapeObjectProperties); node = t.callExpression(t.memberExpression(node, t.identifier('shape')), [shapeObjectExpression]); } else if (method === 'shape-intersect-runtime') { // Return shape, not object node = makeShapeAstForShapeIntersectRuntime(data); } else if (method === 'arrayOf') { node = t.callExpression(t.memberExpression(node, t.identifier('arrayOf')), [makePropType(data.of)]); } else if (method === 'objectOf') { node = t.callExpression(t.memberExpression(node, t.identifier('objectOf')), [makePropType(data.of)]); } else if (method === 'oneOf') { node = t.callExpression(t.memberExpression(node, t.identifier('oneOf')), [t.arrayExpression(data.options.map(_util.makeLiteral))]); } else if (method === 'oneOfType') { node = t.callExpression(t.memberExpression(node, t.identifier('oneOfType')), [t.arrayExpression(data.options.map(function (item) { return makePropType(item); }))]); } else if (method === 'reference') { var pp = data.propertyPath.slice(); var valueNode = t.identifier(pp.shift()); while (pp.length) { valueNode = t.memberExpression(valueNode, t.identifier(pp.shift())); } node = t.callExpression(t.memberExpression(node, t.identifier('shape')), [valueNode]); } else if (method === 'void') { markFullExpressionAsRequired = false; node = dontSetTemplate().expression; } else if (method === 'possible-class') { markFullExpressionAsRequired = false; var classSpec; if (data.value.name != null) { classSpec = t.identifier(data.value.name); } else if (data.value.type === 'QualifiedTypeIdentifier') { classSpec = processQualifiedTypeIdentifierIntoMemberExpression(data.value); } else { throw new Error('Unknown node type in possible-class for node:', data.value); } var instanceOfNode = t.callExpression(t.memberExpression(node, t.identifier('instanceOf')), [classSpec]); var anyNode = makeAnyPropTypeAST(); if (data.isRequired) { instanceOfNode = markNodeAsRequired(instanceOfNode); anyNode = markNodeAsRequired(anyNode); } var _functionCheckNode = makeFunctionCheckAST(classSpec); node = t.conditionalExpression(_functionCheckNode, instanceOfNode, anyNode); // Don't add .required on the full expression; we already handled this ourselves // for any proptypes we generated // Wrap it in a function for ES6 imports with circular dependencies node = deferTpl({ NODE: node }).expression; } else { var bugData = JSON.stringify(data, null, 2); (0, _util.$debug)('Unknown node ' + bugData); throw new Error("".concat(_util.PLUGIN_NAME, " processing error: This is an internal error that should never happen. ") + "Report it immediately with the source file and babel config. Data: ".concat(bugData)); } if (markFullExpressionAsRequired && data.isRequired) { node = markNodeAsRequired(node); } return t.cloneDeep(node); }