modules/material-parser/src/parse/js/handlers/defaultPropsHandler.ts (118 lines of code) (raw):
import getComposedPath from '../utils/getComposedPath';
import evaluate from '../utils/evaluate';
const { namedTypes: t, NodePath, visit } = require('ast-types');
type NodePathType = typeof NodePath;
const {
getPropertyName,
isReactComponentClass,
getMemberValuePath,
isReactForwardRefCall,
printValue,
resolveToValue,
} = require('react-docgen').utils;
const resolveFunctionDefinitionToReturnValue = require('react-docgen/dist/utils/resolveFunctionDefinitionToReturnValue');
function getDefaultValue(path: NodePathType) {
let { node } = path;
let defaultValue;
if (t.Literal.check(node)) {
defaultValue = node.raw;
} else {
if (t.AssignmentPattern.check(path.node)) {
path = resolveToValue(path.get('right'));
} else {
path = resolveToValue(path);
}
if (t.ImportDeclaration.check(path.node)) {
defaultValue = node.name;
} else {
node = path.node;
try {
const result = evaluate(path);
if (result.confident) {
defaultValue = result.value;
}
} catch (e) {
// log(e);
// TODO
}
}
}
if (typeof defaultValue !== 'undefined') {
return {
value: defaultValue,
computed:
t.CallExpression.check(node) || t.MemberExpression.check(node) || t.Identifier.check(node),
};
}
return null;
}
function getStatelessPropsPath(componentDefinition: any) {
const value = resolveToValue(componentDefinition);
if (isReactForwardRefCall(value)) {
const inner = resolveToValue(value.get('arguments', 0));
return inner.get('params', 0);
}
return value.get('params', 0);
}
function getDefaultPropsPath(componentDefinition: any) {
let defaultPropsPath = getMemberValuePath(componentDefinition, 'defaultProps');
if (!defaultPropsPath) {
return null;
}
defaultPropsPath = resolveToValue(defaultPropsPath);
if (!defaultPropsPath) {
return null;
}
if (t.FunctionExpression.check(defaultPropsPath.node)) {
// Find the value that is returned from the function and process it if it is
// an object literal.
const returnValue = resolveFunctionDefinitionToReturnValue(defaultPropsPath);
if (returnValue && t.ObjectExpression.check(returnValue.node)) {
defaultPropsPath = returnValue;
}
}
return defaultPropsPath;
}
function getDefaultValuesFromProps(properties: any[], documentation: any, isStateless: boolean) {
properties
// Don't evaluate property if component is functional and the node is not an AssignmentPattern
.filter(
(propertyPath) => !isStateless || t.AssignmentPattern.check(propertyPath.get('value').node),
)
.forEach((propertyPath) => {
if (t.Property.check(propertyPath.node)) {
const propName = getPropertyName(propertyPath);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
const defaultValue = getDefaultValue(
isStateless ? propertyPath.get('value', 'right') : propertyPath.get('value'),
);
if (defaultValue) {
propDescriptor.defaultValue = defaultValue;
}
} else if (t.SpreadElement.check(propertyPath.node)) {
const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
if (t.ObjectExpression.check(resolvedValuePath.node)) {
getDefaultValuesFromProps(
resolvedValuePath.get('properties'),
documentation,
isStateless,
);
}
}
});
}
export default function defaultPropsHandler(documentation: any, componentDefinition: any) {
let statelessProps = null;
let defaultPropsPath = getDefaultPropsPath(componentDefinition);
/**
* function, lazy, memo, forwardRef etc components can resolve default props as well
*/
if (!isReactComponentClass(componentDefinition)) {
statelessProps = getStatelessPropsPath(componentDefinition);
}
// Do both statelessProps and defaultProps if both are available so defaultProps can override
if (statelessProps && t.ObjectPattern.check(statelessProps.node)) {
getDefaultValuesFromProps(statelessProps.get('properties'), documentation, true);
}
if (defaultPropsPath && !t.ObjectExpression.check(defaultPropsPath.node)) {
const composedPath = getComposedPath(documentation, 'defaultProps', defaultPropsPath);
if (composedPath) {
defaultPropsPath = composedPath;
}
}
if (defaultPropsPath && t.ObjectExpression.check(defaultPropsPath.node)) {
getDefaultValuesFromProps(defaultPropsPath.get('properties'), documentation, false);
}
}