Merge branch feat/material-parser-jsdoc into release/1.0.0

Title: 支持解析propType上方的jsdoc 

1. 解析func类型propType上方的jsdoc,获取params和returns
2. 解析ts interface 的返回值,获取returns(params已在上个迭代完成)
3. 在func类型中添加params、returns和raw字段,分别记录其入参、出参和函数签名

Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3932322
This commit is contained in:
rongbin.arb 2020-10-20 10:36:15 +08:00
commit fc99b3d9ca
14 changed files with 22612 additions and 11498 deletions

View File

@ -31,6 +31,7 @@ export default async (args: IParseArgs) => {
try { try {
return parseJS(moduleFileAbsolutePath); return parseJS(moduleFileAbsolutePath);
} catch (e) { } catch (e) {
log(e);
await install(args); await install(args);
const info = parseDynamic(mainFileAbsolutePath); const info = parseDynamic(mainFileAbsolutePath);
if (!info || !info.length) { if (!info || !info.length) {

View File

@ -3,6 +3,7 @@ import defaultPropsHandler from './defaultPropsHandler';
import flowTypeHandler from './flowTypeHandler'; import flowTypeHandler from './flowTypeHandler';
import componentMethodsHandler from './componentMethodsHandler'; import componentMethodsHandler from './componentMethodsHandler';
import preProcessHandler from './preProcessHandler'; import preProcessHandler from './preProcessHandler';
import propTypeJsDocHandler from './propTypeJsDocHandler';
const { handlers } = require('react-docgen'); const { handlers } = require('react-docgen');
@ -13,6 +14,7 @@ const defaultHandlers = [
childContextTypeHandler, childContextTypeHandler,
handlers.propTypeCompositionHandler, handlers.propTypeCompositionHandler,
handlers.propDocBlockHandler, handlers.propDocBlockHandler,
propTypeJsDocHandler,
flowTypeHandler, flowTypeHandler,
defaultPropsHandler, defaultPropsHandler,
handlers.componentDocblockHandler, handlers.componentDocblockHandler,

View File

@ -0,0 +1,64 @@
/* eslint-disable no-param-reassign */
import { set, get } from 'lodash';
import { debug } from '../../../core';
const log = debug.extend('parse:js');
const parseJsDoc = require('react-docgen/dist/utils/parseJsDoc').default;
const { getMemberValuePath, resolveToValue } = require('react-docgen').utils;
function getType(type = 'void') {
const typeOfType = typeof type;
if (typeOfType === 'string') {
return typeOfType;
} else if (typeOfType === 'object') {
return get(type, 'name', 'void');
}
return 'void';
}
function generateRaw(params = [], returns = { type: 'void' }): string {
const raw = `(${params.filter(x => !!x).map(x => `${x.name}: ${getType(x.type)}`).join(', ')}) => ${returns ? getType(returns.type) : 'void'}`;
return raw;
}
function resolveDocumentation(documentation) {
documentation._props.forEach(propDescriptor => {
const { description } = propDescriptor;
if (description.includes('@') && propDescriptor?.type?.name === 'func') {
const jsDoc = parseJsDoc(description);
propDescriptor.description = jsDoc.description;
if (jsDoc.params) {
set(propDescriptor, ['type', 'params'], jsDoc.params);
}
if (jsDoc.returns) {
set(propDescriptor, ['type', 'returns'], jsDoc.returns);
}
try {
const raw = generateRaw(jsDoc.params, jsDoc.returns);
if (raw) {
set(propDescriptor, ['type', 'raw'], raw);
}
} catch (e) {
log(e);
}
}
});
}
/**
* Extract info from the propType jsdoc blocks. Must be run after
* propDocBlockHandler.
*/
export default function propTypeJsDocHandler(documentation, path) {
let propTypesPath = getMemberValuePath(path, 'propTypes');
if (!propTypesPath) {
return;
}
propTypesPath = resolveToValue(propTypesPath);
if (!propTypesPath) {
return;
}
resolveDocumentation(documentation);
}

View File

@ -6,7 +6,17 @@ const log = debug.extend('parse:transform');
export function transformType(itemType: any) { export function transformType(itemType: any) {
if (typeof itemType === 'string') return itemType; if (typeof itemType === 'string') return itemType;
const { name, elements, value = elements, computed, required, type, raw } = itemType; const {
name,
elements,
value = elements,
computed,
required,
type,
raw,
params,
returns,
} = itemType;
if (computed !== undefined && value) { if (computed !== undefined && value) {
return safeEval(value); return safeEval(value);
} }
@ -27,13 +37,27 @@ export function transformType(itemType: any) {
case 'array': case 'array':
case 'element': case 'element':
case 'node': case 'node':
case 'void':
break; break;
case 'func': case 'func':
if (value) { if (params) {
result.value = value.map(x => ({ result.params = params.map(x => {
...x, const res: any = {
propType: transformType(x.propType), 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; result.raw = raw;
} }
break; break;
@ -225,7 +249,8 @@ export function transformItem(name: string, item: any) {
flowType, flowType,
tsType, tsType,
type = tsType || flowType, type = tsType || flowType,
required, optional,
required = optional,
defaultValue, defaultValue,
...others ...others
} = item; } = item;

View File

@ -1,5 +1,5 @@
import { Parser, ComponentDoc } from 'react-docgen-typescript'; import { Parser, ComponentDoc } from 'react-docgen-typescript';
import ts, { SymbolFlags, TypeFlags } from 'typescript'; import ts, { SymbolFlags, TypeFlags, SyntaxKind } from 'typescript';
import { isEmpty, isEqual } from 'lodash'; import { isEmpty, isEqual } from 'lodash';
import { debug } from '../../core'; import { debug } from '../../core';
import { Json } from '../../types'; import { Json } from '../../types';
@ -43,20 +43,12 @@ function getFunctionParams(parameters: any[] = [], checker, parentIds, type) {
}); });
} }
/** function getFunctionReturns(node: any, checker, parentIds, type) {
* Indicates that a symbol is an alias that does not merge with a local declaration. const propType = getDocgenTypeHelper(checker, node.type, false, getNextParentIds(parentIds, type));
* OR Is a JSContainer which may merge an alias with a local declaration return {
*/ propType,
// function isNonLocalAlias( };
// symbol: ts.Symbol | undefined, }
// excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace,
// ): symbol is ts.Symbol {
// if (!symbol) return false;
// return (
// (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias ||
// !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment)
// );
// }
const blacklistNames = [ const blacklistNames = [
'prototype', 'prototype',
@ -230,6 +222,14 @@ function getDocgenTypeHelper(
} }
} }
// @ts-ignore
if (type?.kind === SyntaxKind.VoidExpression) {
return makeResult({
name: 'void',
raw: 'void',
});
}
const pattern = /^__global\.(.+)$/; const pattern = /^__global\.(.+)$/;
// @ts-ignore // @ts-ignore
if (parentIds.includes(type?.symbol?.id)) { if (parentIds.includes(type?.symbol?.id)) {
@ -281,9 +281,7 @@ function getDocgenTypeHelper(
return makeResult({ return makeResult({
name: 'union', name: 'union',
// @ts-ignore // @ts-ignore
value: type.types.map(t => value: type.types.map(t => getDocgenTypeHelper(checker, t, true, getNextParentIds(parentIds, type))),
getDocgenTypeHelper(checker, t, true, getNextParentIds(parentIds, type)),
),
}); });
} else if (isComplexType(type)) { } else if (isComplexType(type)) {
return makeResult({ return makeResult({
@ -321,13 +319,19 @@ function getDocgenTypeHelper(
} else if (type?.symbol?.valueDeclaration?.parameters?.length) { } else if (type?.symbol?.valueDeclaration?.parameters?.length) {
return makeResult({ return makeResult({
name: 'func', name: 'func',
value: getFunctionParams( params: getFunctionParams(
// @ts-ignore // @ts-ignore
type?.symbol?.valueDeclaration?.parameters, type?.symbol?.valueDeclaration?.parameters,
checker, checker,
parentIds, parentIds,
type, type,
), ),
returns: getFunctionReturns(
checker.typeToTypeNode(type, type?.symbol?.valueDeclaration),
checker,
parentIds,
type,
),
}); });
} else if ( } else if (
// @ts-ignore // @ts-ignore
@ -335,7 +339,7 @@ function getDocgenTypeHelper(
) { ) {
return makeResult({ return makeResult({
name: 'func', name: 'func',
value: getFunctionParams( params: getFunctionParams(
// @ts-ignore // @ts-ignore
type?.members?.get('__call')?.declarations[0]?.symbol?.declarations[0]?.parameters, type?.members?.get('__call')?.declarations[0]?.symbol?.declarations[0]?.parameters,
checker, checker,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -327,8 +327,27 @@ Generated by [AVA](https://avajs.dev).
propType: 'bool', propType: 'bool',
}, },
{ {
description: 'desc',
name: 'optionalFunc', name: 'optionalFunc',
propType: 'func', propType: {
params: [
{
description: 'The title of the book.',
name: 'title',
propType: 'string',
},
{
description: 'The author of the book.',
name: 'author',
propType: 'string',
},
],
raw: '(title: string, author: string) => any',
returns: {
propType: 'any',
},
type: 'func',
},
}, },
{ {
defaultValue: 123, defaultValue: 123,
@ -547,6 +566,30 @@ Generated by [AVA](https://avajs.dev).
version: '1.0.0', version: '1.0.0',
}, },
props: [ props: [
{
name: 'error',
propType: {
isRequired: true,
params: [
{
name: 'a',
propType: 'string',
},
],
raw: '(a: string) => number',
returns: {
propType: 'number',
},
type: 'func',
},
},
{
name: 'void',
propType: {
isRequired: true,
type: 'void',
},
},
{ {
name: 'object', name: 'object',
propType: { propType: {
@ -640,9 +683,7 @@ Generated by [AVA](https://avajs.dev).
{ {
name: 'fun', name: 'fun',
propType: { propType: {
raw: '(a: string[]) => void', params: [
type: 'func',
value: [
{ {
name: 'a', name: 'a',
propType: { propType: {
@ -651,6 +692,11 @@ Generated by [AVA](https://avajs.dev).
}, },
}, },
], ],
raw: '(a: string[]) => void',
returns: {
propType: 'number',
},
type: 'func',
}, },
}, },
], ],
@ -667,14 +713,17 @@ Generated by [AVA](https://avajs.dev).
name: 'func', name: 'func',
propType: { propType: {
isRequired: true, isRequired: true,
raw: '{ (arg: string): number; (a: string): Element; }', params: [
type: 'func',
value: [
{ {
name: 'arg', name: 'arg',
propType: 'string', propType: 'string',
}, },
], ],
raw: '(arg: string) => number',
returns: {
propType: 'number',
},
type: 'func',
}, },
}, },
{ {
@ -693,9 +742,7 @@ Generated by [AVA](https://avajs.dev).
{ {
name: 'a', name: 'a',
propType: { propType: {
raw: '(arg: string, num: number) => void', params: [
type: 'func',
value: [
{ {
name: 'arg', name: 'arg',
propType: 'string', propType: 'string',
@ -705,6 +752,11 @@ Generated by [AVA](https://avajs.dev).
propType: 'number', propType: 'number',
}, },
], ],
raw: '(arg: string, num: number) => void',
returns: {
propType: 'number',
},
type: 'func',
}, },
}, },
], ],
@ -778,14 +830,17 @@ Generated by [AVA](https://avajs.dev).
name: 'refFunc', name: 'refFunc',
propType: { propType: {
isRequired: true, isRequired: true,
raw: '(p: Props) => void', params: [
type: 'func',
value: [
{ {
name: 'p', name: 'p',
propType: 'object', propType: 'object',
}, },
], ],
raw: '(p: Props) => void',
returns: {
propType: 'number',
},
type: 'func',
}, },
}, },
{ {
@ -796,14 +851,14 @@ Generated by [AVA](https://avajs.dev).
value: [ value: [
'element', 'element',
{ {
raw: 'Func', params: [
type: 'func',
value: [
{ {
name: 'a', name: 'a',
propType: 'string', propType: 'string',
}, },
], ],
raw: 'Func',
type: 'func',
}, },
], ],
}, },
@ -908,14 +963,14 @@ Generated by [AVA](https://avajs.dev).
name: 'func2', name: 'func2',
propType: { propType: {
isRequired: true, isRequired: true,
raw: 'Func', params: [
type: 'func',
value: [
{ {
name: 'a', name: 'a',
propType: 'string', propType: 'string',
}, },
], ],
raw: 'Func',
type: 'func',
}, },
}, },
{ {

View File

@ -1,19 +1,19 @@
import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
import _createClass from "@babel/runtime/helpers/createClass"; import _createClass from '@babel/runtime/helpers/createClass';
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; import _possibleConstructorReturn from '@babel/runtime/helpers/possibleConstructorReturn';
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; import _getPrototypeOf from '@babel/runtime/helpers/getPrototypeOf';
import _inherits from "@babel/runtime/helpers/inherits"; import _inherits from '@babel/runtime/helpers/inherits';
/* eslint-disable react/no-unused-prop-types */ /* eslint-disable react/no-unused-prop-types */
/* eslint-disable react/require-default-props */ /* eslint-disable react/require-default-props */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import "./main.css"; import './main.css';
const Demo = const Demo =
/* #__PURE__ */ /* #__PURE__ */
function (_React$Component) { (function(_React$Component) {
_inherits(Demo, _React$Component); _inherits(Demo, _React$Component);
function Demo() { function Demo() {
@ -22,19 +22,27 @@ function (_React$Component) {
return _possibleConstructorReturn(this, _getPrototypeOf(Demo).apply(this, arguments)); return _possibleConstructorReturn(this, _getPrototypeOf(Demo).apply(this, arguments));
} }
_createClass(Demo, [{ _createClass(Demo, [
key: "render", {
key: 'render',
value: function render() { value: function render() {
return React.createElement("div", null, " Test "); return React.createElement('div', null, ' Test ');
}, },
}]); },
]);
return Demo; return Demo;
}(React.Component); })(React.Component);
Demo.propTypes = { Demo.propTypes = {
optionalArray: PropTypes.array, optionalArray: PropTypes.array,
optionalBool: PropTypes.bool, optionalBool: PropTypes.bool,
/**
* desc
* @param {string} title - The title of the book.
* @param {string} author - The author of the book.
* @returns {any}
*/
optionalFunc: PropTypes.func, optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number, optionalNumber: PropTypes.number,
optionalObject: PropTypes.object, optionalObject: PropTypes.object,
@ -54,7 +62,11 @@ Demo.propTypes = {
// it as an enum. // it as an enum.
optionalEnum: PropTypes.oneOf(['News', 'Photos']), optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// An object that could be one of many types // An object that could be one of many types
optionalUnion: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Demo)]), optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Demo),
]),
// An array of a certain type // An array of a certain type
optionalArrayOf: PropTypes.arrayOf(PropTypes.number), optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// An object with property values of a certain type // An object with property values of a certain type

View File

@ -14,6 +14,12 @@ class Demo extends React.Component {
Demo.propTypes = { Demo.propTypes = {
optionalArray: PropTypes.array, optionalArray: PropTypes.array,
optionalBool: PropTypes.bool, optionalBool: PropTypes.bool,
/**
* desc
* @param {string} title - The title of the book.
* @param {string} author - The author of the book.
* @returns {any}
*/
optionalFunc: PropTypes.func, optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number, optionalNumber: PropTypes.number,
optionalObject: PropTypes.object, optionalObject: PropTypes.object,

View File

@ -24,6 +24,8 @@ type Union =
}; };
interface Props { interface Props {
error(a: string): number;
void: void;
object: Object; object: Object;
trigger?: Array<'click' | 'hover' | 'contextMenu'>; trigger?: Array<'click' | 'hover' | 'contextMenu'>;
str?: string; str?: string;
@ -75,7 +77,6 @@ interface Props {
elementType?: React.ElementType; elementType?: React.ElementType;
union: Union; union: Union;
// eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures // eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures
func(a: string): JSX.Element;
func2: Func; func2: Func;
html: HTMLBaseElement; html: HTMLBaseElement;
loading?: boolean | { delay?: number }; loading?: boolean | { delay?: number };
@ -93,8 +94,8 @@ App.defaultProps = {
a: '1', a: '1',
b: '2', b: '2',
}, },
func(a) { func(a: string) {
return a; return 123;
}, },
str: 'str', str: 'str',
}; };