2022-03-30 15:47:02 +08:00

307 lines
7.5 KiB
TypeScript

import { omit, pick, isNil, uniq } from 'lodash';
import { safeEval, isEvaluable } from '../utils';
import { debug } from '../core';
const log = debug.extend('parse:transform');
export function transformType(itemType: any) {
if (typeof itemType === 'string') return itemType;
const {
name,
elements,
value = elements,
computed,
required,
type,
raw,
params,
returns,
} = itemType;
if (computed !== undefined && value) {
return safeEval(value);
}
const result: any = {
type: name,
};
if (required) {
result.isRequired = required;
}
switch (name) {
case 'number':
case 'string':
case 'bool':
case 'any':
case 'symbol':
case 'object':
case 'null':
case 'array':
case 'element':
case 'node':
case 'void':
break;
case 'func':
if (params) {
result.params = params.map((x) => {
const res: any = {
name: x.name,
propType: transformType(x.type || x.propType),
};
if (x.description) {
res.description = x.description;
}
return res;
});
}
if (returns) {
result.returns = {
propType: transformType(returns.type || returns.propType),
};
}
if (raw) {
result.raw = raw;
}
break;
case 'literal': {
result.type = 'oneOf';
try {
const literalValue = safeEval(value);
result.value = [literalValue];
} catch (e) {
result.value = [raw];
}
break;
}
case 'enum':
case 'oneOf':
result.type = 'oneOf';
result.value = value.map(transformType);
break;
case 'tuple':
result.type = 'tuple';
result.value = value.map(transformType);
break;
case 'union': {
if (itemType.raw) {
if (itemType.raw.match(/ReactNode$/)) {
result.type = 'node';
break;
} else if (itemType.raw.match(/Element$/)) {
result.type = 'element';
break;
}
}
}
// eslint-disable-next-line no-fallthrough
case 'oneOfType':
result.type = 'oneOfType';
result.value = value.map(transformType);
break;
case 'boolean':
result.type = 'bool';
break;
case 'Function':
result.type = 'func';
break;
case 'unknown':
result.type = 'any';
break;
case 'Array':
case 'arrayOf': {
result.type = 'arrayOf';
let _itemType = transformType(value[0]);
if (typeof _itemType === 'object') {
_itemType = omit(_itemType, ['isRequired']);
}
result.value = _itemType;
break;
}
case 'signature': {
if (typeof type === 'string') {
result.type = type;
break;
}
result.type = 'shape';
const properties = type?.signature?.properties || itemType?.signature?.properties || [];
if (properties.length === 0) {
if (raw?.includes('=>')) {
result.type = 'func';
result.raw = raw;
} else {
result.type = 'object';
}
} else if (properties.length === 1 && typeof properties[0].key === 'object') {
const v = transformType(properties[0].value);
if (v === 'any') {
result.type = 'object';
} else if (typeof v === 'string') {
result.value = v;
result.type = 'objectOf';
} else if (typeof v?.type === 'string') {
result.value = v.type;
result.type = 'objectOf';
} else {
result.type = 'object';
}
} else if (properties.length === 1 && properties[0].key === '__call') {
result.type = 'func';
} else {
result.value = properties
.filter((item: any) => typeof item.key !== 'object')
.map((prop: any) => {
const { key } = prop;
const typeItem = {
...omit(prop.value, 'name'),
type: prop.value.type || {},
};
typeItem.type = {
...typeItem.type,
...pick(prop.value, ['name', 'value']),
};
return transformItem(key, typeItem);
});
}
break;
}
case 'objectOf':
case 'instanceOf':
result.value = transformType(value);
break;
case 'exact':
case 'shape':
result.value = Object.keys(value).map((n) => {
const { name: _name, ...others } = value[n];
return transformItem(n, {
...others,
type: {
name: _name,
},
});
});
break;
case (name.match(/ReactNode$/) || {}).input:
result.type = 'node';
break;
case (name.match(/JSX\.Element$/) || {}).input:
result.type = 'element';
break;
default:
result.type = 'object';
break;
}
if (Object.keys(result).length === 1) {
return result.type;
}
if (result?.type === 'oneOfType') {
return combineOneOfValues(result);
}
return result;
}
function combineOneOfValues(propType) {
if (propType.type !== 'oneOfType') {
return propType;
}
const newValue = [];
let oneOfItem = null;
let firstBooleanIndex = -1;
propType.value.forEach((item) => {
if (item?.type === 'oneOf') {
if (!oneOfItem) {
oneOfItem = {
type: 'oneOf',
value: [],
};
}
if (item.value.includes(true) || item.value.includes(false)) {
if (firstBooleanIndex !== -1) {
oneOfItem.value.splice(firstBooleanIndex, 1);
newValue.push('bool');
} else {
firstBooleanIndex = oneOfItem.value.length;
oneOfItem.value = oneOfItem.value.concat(item.value);
}
} else {
oneOfItem.value = oneOfItem.value.concat(item.value);
}
} else {
newValue.push(item);
}
});
let result = propType;
const oneOfItemLength = oneOfItem?.value?.length;
if (oneOfItemLength) {
newValue.push(oneOfItem);
}
if (firstBooleanIndex !== -1 || oneOfItemLength) {
result = {
...propType,
value: newValue,
};
}
if (result.value.length === 1 && result.value[0]?.type === 'oneOf') {
result = {
...result,
type: 'oneOf',
value: result.value[0].value,
};
}
result.value = uniq(result.value);
return result;
}
export function transformItem(name: string, item: any) {
const {
description,
flowType,
tsType,
type = tsType || flowType,
optional,
required = optional,
defaultValue,
...others
} = item;
const result: any = {
name,
};
if (type) {
result.propType = transformType({
...type,
...omit(others, ['name']),
required: !!required,
});
}
if (description) {
if (description.includes('\n')) {
result.description = description.split('\n')[0];
} else {
result.description = description;
}
}
if (!isNil(defaultValue) && typeof defaultValue === 'object' && isEvaluable(defaultValue)) {
if (defaultValue === null) {
result.defaultValue = defaultValue;
} else if ('computed' in defaultValue) {
// parsed data from react-docgen
try {
if (isEvaluable(defaultValue.value)) {
result.defaultValue = safeEval(defaultValue.value);
} else {
result.defaultValue = defaultValue.value;
}
} catch (e) {
log(e);
}
} else {
// parsed data from react-docgen-typescript
result.defaultValue = defaultValue.value;
}
}
if (result.propType === undefined) {
delete result.propType;
}
return result;
}