babel-plugin-flow-react-proptypes
Version:
converts flow types to react proptypes
447 lines (389 loc) • 17.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = convertToPropTypes;
var _util = require("./util");
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function getObjectTypePropertyKey(node) {
if (node.key.type === 'StringLiteral') {
return node.key.value;
}
return node.key.name;
}
function mapGenericToRealType(node, typeParamMapping) {
if (node.type === 'GenericTypeAnnotation' && node.id.type === 'Identifier') {
return typeParamMapping[node.id.name] || node;
} else if (node.type === 'NullableTypeAnnotation') {
var realTypeAnnotation = mapGenericToRealType(node.typeAnnotation, typeParamMapping);
var newNode = Object.assign({}, node, {
typeAnnotation: realTypeAnnotation
});
return newNode;
} else if (node.type === 'UnionTypeAnnotation') {
var realTypes = node.types.map(function (n) {
return mapGenericToRealType(n, typeParamMapping);
});
var _newNode = Object.assign({}, node, {
types: realTypes
});
return _newNode;
} else if (node.type === 'ObjectTypeProperty') {
var realValue = mapGenericToRealType(node.value, typeParamMapping);
var _newNode2 = Object.assign({}, node, {
value: realValue
});
return _newNode2;
} else if (node.type === 'ObjectTypeAnnotation') {
var realProperties = node.properties.map(function (p) {
return mapGenericToRealType(p, typeParamMapping);
});
var _newNode3 = Object.assign({}, node, {
properties: realProperties
});
return _newNode3;
}
return node;
}
function convertGenericToPropTypes(node, typeParamMapping, importedTypes, internalTypes) {
if (node.type === 'GenericTypeAnnotation' && node.id.type === 'Identifier' && !node.typeParameters) {
return convertToPropTypes(typeParamMapping[node.id.name], importedTypes, internalTypes);
} else if (node.type === 'GenericTypeAnnotation' && node.typeParameters) {
var realTypeParameters = node.typeParameters.params.map(function (p) {
return mapGenericToRealType(p, typeParamMapping);
});
return internalTypes[node.id.name](realTypeParameters);
} else if (node.type === 'ArrayTypeAnnotation') {
var realElementType = mapGenericToRealType(node.elementType, typeParamMapping);
var realNode = Object.assign({}, node, {
elementType: realElementType
});
return convertToPropTypes(realNode, importedTypes, internalTypes);
} else if (node.type === 'NullableTypeAnnotation') {
var result = convertGenericToPropTypes(node.typeAnnotation, typeParamMapping, importedTypes, internalTypes);
result.optional = true;
return result;
} else if (node.type === 'UnionTypeAnnotation') {
var types = node.types.map(function (n) {
return mapGenericToRealType(n, typeParamMapping);
});
var _realNode = Object.assign({}, node, {
types: types
});
return convertToPropTypes(_realNode, importedTypes, internalTypes);
} else if (node.type === 'ObjectTypeAnnotation') {
var properties = node.properties.map(function (n) {
return mapGenericToRealType(n, typeParamMapping);
});
var _realNode2 = Object.assign({}, node, {
properties: properties
});
return convertToPropTypes(_realNode2, importedTypes, internalTypes);
}
}
function convertToPropTypes(node, importedTypes, internalTypes) {
(0, _util.$debug)('convertToPropTypes', node);
var resultPropType;
if (node.type === 'TypeAlias' && node.typeParameters) {
return function (types) {
var typeParams = node.typeParameters.params.map(function (t) {
return t.name;
});
var typeParamMapping = {};
for (var i = 0; i < typeParams.length; i++) {
typeParamMapping[typeParams[i]] = types[i];
}
return convertGenericToPropTypes(node.right, typeParamMapping, importedTypes, internalTypes);
};
} else if (node.right) {
node = node.right;
}
if (node.type === 'OpaqueType') {
node = node.impltype;
}
if (node.type === 'InterfaceDeclaration') {
node = node.body;
}
if (node.type === 'ObjectTypeAnnotation') {
var properties = [];
if (node.indexers.length === 1 && node.properties.length === 0) {
// indexed object, like `{[name: string]: number}`
// recur on indexer's value type (`number` in the example above)
// and emit an `objectOf`
var indexer = node.indexers[0];
var subnode = indexer.value;
var subresult = convertToPropTypes(subnode, importedTypes, internalTypes);
subresult.isRequired = !subresult.optional;
resultPropType = {
type: 'objectOf',
of: subresult
};
} else {
// not an indexed object
// recurse on object properties
node.properties.forEach(function (subnode) {
// result may be from:
// ObjectTypeProperty - {key, value}
// ObjectTypeSpreadProperty - Array<{key, value}>
var result = convertToPropTypes(subnode, importedTypes, internalTypes);
if (subnode.leadingComments && subnode.leadingComments.length) {
result.leadingComments = subnode.leadingComments;
}
if (Array.isArray(result)) {
result.forEach(function (prop) {
return properties.push(prop);
});
} else {
properties.push(result);
}
}); // return a shape
resultPropType = {
type: 'shape',
properties: properties,
isExact: node.exact
};
}
} else if (node.type === 'ObjectTypeProperty') {
var key = getObjectTypePropertyKey(node);
var value = node.value; // recurse
value = convertToPropTypes(value, importedTypes, internalTypes); // handles id?: string
if (value) {
value.isRequired = !node.optional && !value.optional;
}
return {
key: key,
value: value
};
} else if (node.type === 'ObjectTypeSpreadProperty') {
var exact = (0, _util.isExact)(node.argument);
var _subnode;
if (exact) {
_subnode = node.argument.typeParameters.params[0];
} else {
_subnode = node.argument;
}
var spreadShape = convertToPropTypes(_subnode, importedTypes, internalTypes);
if (spreadShape.type === 'raw') {
return [];
}
var _properties = spreadShape.properties; // Unless or until the strange default behavior changes in flow (https://github.com/facebook/flow/issues/3214)
// every property from spread becomes optional unless it uses `...$Exact<T>`
// @see also explanation of behavior - https://github.com/facebook/flow/issues/3534#issuecomment-287580240
// @returns flattened properties from shape
if (!exact) {
_properties.forEach(function (prop) {
return prop.value.isRequired = false;
});
}
return _properties;
} else if (node.type === 'FunctionTypeAnnotation') resultPropType = {
type: 'func'
};else if (node.type === 'AnyTypeAnnotation') resultPropType = {
type: 'any'
}; // babylon6 Node Name
else if (node.type === 'ExistentialTypeParam') resultPropType = {
type: 'any'
}; // babylon7 Node Name
else if (node.type === 'ExistsTypeAnnotation') resultPropType = {
type: 'any'
};else if (node.type === 'MixedTypeAnnotation') resultPropType = {
type: 'any'
};else if (node.type === 'TypeofTypeAnnotation') resultPropType = {
type: 'any'
};else if (node.type === 'EmptyTypeAnnotation') resultPropType = {
type: 'any'
};else if (node.type === 'NumberTypeAnnotation') resultPropType = {
type: 'number'
};else if (node.type === 'StringTypeAnnotation') resultPropType = {
type: 'string'
};else if (node.type === 'BooleanTypeAnnotation') resultPropType = {
type: 'bool'
};else if (node.type === 'VoidTypeAnnotation') resultPropType = {
type: 'void'
};else if (node.type === 'TupleTypeAnnotation') resultPropType = {
type: 'arrayOf',
of: {
type: 'any'
}
};else if (node.type === 'NullableTypeAnnotation') {
resultPropType = convertToPropTypes(node.typeAnnotation, importedTypes, internalTypes);
resultPropType.optional = true;
} else if (node.type === 'IntersectionTypeAnnotation') {
var _ref;
var objectTypeAnnotations = node.types.filter(function (annotation) {
return annotation.type === 'ObjectTypeAnnotation' || annotation.type === 'GenericTypeAnnotation';
});
var propTypes = objectTypeAnnotations.map(function (node) {
return convertToPropTypes(node, importedTypes, internalTypes);
});
var shapes = propTypes.filter(function (propType) {
return propType.type === 'shape';
});
var requiresRuntimeMerge = propTypes.filter(function (propType) {
return propType.type === 'raw' || propType.type === 'shape-intersect-runtime';
});
var mergedProperties = (_ref = []).concat.apply(_ref, _toConsumableArray(shapes.map(function (propType) {
return propType.properties;
})));
if (mergedProperties.length === 0 && requiresRuntimeMerge.length === 0) {
resultPropType = {
type: 'any'
};
} else if (requiresRuntimeMerge.length === 0) {
resultPropType = {
type: 'shape',
properties: mergedProperties
};
} else {
// TODO: properties may be a misnomer - that probably means a list of object
// property specifications
resultPropType = {
'type': 'shape-intersect-runtime',
properties: propTypes
};
}
} // https://github.com/brigand/babel-plugin-flow-react-proptypes/issues/147
else if (node.type === 'GenericTypeAnnotation' && node.id.type === 'QualifiedTypeIdentifier' && node.id.qualification && node.id.qualification.name === 'React' && node.id.id && node.id.id.name === 'ElementProps') {
var tp = node.typeParameters && node.typeParameters.params;
if (!tp || tp.length !== 1) {
throw new TypeError("babel-plugin-flow-react-proptypes expected React.ElementProps to have one type parameter");
}
if (tp[0].type === 'StringLiteralTypeAnnotation') {
resultPropType = {
type: 'object',
properties: []
};
} else if (tp[0].type === 'TypeofTypeAnnotation') {
var argument = tp[0].argument;
var id = argument.id;
if (id.type !== 'Identifier') {
throw new TypeError("babel-plugin-flow-react-proptypes expected React.ElementProps<typeof OneIdentifier>, but found some other type parameter");
}
var name = id.name;
resultPropType = {
type: 'reference',
propertyPath: [name, 'propTypes']
};
} else {
throw new TypeError("babel-plugin-flow-react-proptypes expected React.ElementProps to either be e.g. React.ElementProps<'div'> or React.ElementProps<typeof SomeComponent> ");
}
} // Exact
else if (node.type === 'GenericTypeAnnotation' && node.id.name === '$Exact') {
resultPropType = {
type: 'exact',
properties: convertToPropTypes(node.typeParameters.params[0], importedTypes, internalTypes)
};
} else if (node.type === 'GenericTypeAnnotation' || node.type === 'ArrayTypeAnnotation') {
if (node.type === 'ArrayTypeAnnotation' || node.id.name === 'Array' || node.id.name === '$ReadOnlyArray') {
var arrayType;
if (node.type === 'ArrayTypeAnnotation') {
arrayType = node.elementType;
} else if (!node.typeParameters) {
resultPropType = {
type: 'array'
};
arrayType = null;
} else {
arrayType = node.typeParameters.params[0];
}
if (arrayType && arrayType.type === 'GenericTypeAnnotation' && arrayType.id.type === 'QualifiedTypeIdentifier' && arrayType.id.qualification.name === 'React' && (arrayType.id.id.name === 'Element' || arrayType.id.id.name === 'Node')) {
resultPropType = {
type: 'node'
};
} else if (arrayType) {
var elementType = convertToPropTypes(arrayType, importedTypes, internalTypes);
elementType.isRequired = !elementType.optional;
resultPropType = {
type: 'arrayOf',
of: elementType
};
}
} else if (node.type === 'GenericTypeAnnotation' && node.id.type === 'QualifiedTypeIdentifier' && node.id.qualification.name === 'React' && (node.id.id.name === 'Element' || node.id.id.name === 'Node')) {
resultPropType = {
type: 'node'
};
} else if (node.type === 'GenericTypeAnnotation' && node.typeParameters && typeof internalTypes[node.id.name] === 'function') {
resultPropType = Object.assign({}, internalTypes[node.id.name](node.typeParameters.params));
} else if (node.id && node.id.name && internalTypes[node.id.name]) {
resultPropType = Object.assign({}, internalTypes[node.id.name]);
} else if (node.id && node.id.name && importedTypes[node.id.name]) {
resultPropType = {
type: 'raw',
value: importedTypes[node.id.name].accessNode
};
} else if (node.id.name === 'Object') {
resultPropType = {
type: 'object'
};
} else if (node.id.name === 'Function') {
resultPropType = {
type: 'func'
};
} else {
resultPropType = {
type: 'possible-class',
value: node.id
};
}
} else if (node.type === 'UnionTypeAnnotation') {
var _node = node,
types = _node.types;
var typesWithoutNulls = types.filter(function (t) {
return t.type !== 'NullLiteralTypeAnnotation' && t.type !== 'VoidTypeAnnotation';
}); // If a NullLiteralTypeAnnotation we know that this union type is not required.
var optional = typesWithoutNulls.length !== types.length; // e.g. null | string
// 'foo' | null
if (typesWithoutNulls.length === 1) {
resultPropType = convertToPropTypes(typesWithoutNulls[0], importedTypes, internalTypes);
resultPropType.optional = optional;
} else if (typesWithoutNulls.every(function (t) {
return /Literal/.test(t.type);
})) {
// e.g. 'hello' | 5
resultPropType = {
type: 'oneOf',
optional: optional,
options: typesWithoutNulls.map(function (_ref2) {
var value = _ref2.value;
return value;
})
};
} else {
// e.g. string | number
resultPropType = {
type: 'oneOfType',
optional: optional,
options: typesWithoutNulls.map(function (node) {
return convertToPropTypes(node, importedTypes, internalTypes);
})
};
}
} else if (node.type in {
'StringLiteralTypeAnnotation': 0,
// Babylon6
'NumericLiteralTypeAnnotation': 0,
// Babylon7
'NumberLiteralTypeAnnotation': 0,
'BooleanLiteralTypeAnnotation': 0,
'NullLiteralTypeAnnotation': 0
}) {
var _value = node.value; // babylon7 does not provide value for NullLiteralTypeAnnotation
if (node.type === 'NullLiteralTypeAnnotation') {
_value = true;
}
resultPropType = {
type: 'oneOf',
options: [_value]
};
}
if (resultPropType) {
return resultPropType;
} else {
throw new Error("".concat(_util.PLUGIN_NAME, ": Encountered an unknown node in the type definition. Node: ").concat(JSON.stringify(node)));
}
}