2020-03-17 17:23:42 +08:00

705 lines
26 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { CodeGenerator } from '@babel/generator';
// import { parse } from '@babel/parser';
const buildParser = require('react-docgen/dist/babelParser').default;
const reactDocs = require('react-docgen');
import traverse from '@babel/traverse';
import * as t from '@babel/types';
const { utils: ReactDocUtils } = require('react-docgen');
import { debug } from '../otter-core';
import {
IMaterialParsedModel,
IMaterialScanModel,
IPropType,
IPropTypes,
SourceType,
} from '../types';
import BaseParser from './BaseParser';
import resolver from './resolver';
import handlers from './handlers';
const log = debug.extend('mat');
const parser = buildParser();
function transformType(type: any): any {
const { name, value, computed, required } = type;
if (!value && !required) {
return name;
}
if (computed !== undefined && value) {
// tslint:disable-next-line:no-eval
return eval(value);
}
const result: any = {
type: name,
};
if (required) {
result.isRequired = required;
}
if (Array.isArray(value)) {
if (name === 'enum') {
result.type = 'oneOf';
} else if (name === 'union') {
result.type = 'oneOfType';
}
result.value = value.map(transformType);
} else if (typeof value === 'object') {
if (name === 'objectOf' || name === 'arrayOf' || name === 'instanceOf') {
result.value = transformType(value);
} else {
result.value = Object.keys(value).map((n: string) => {
// tslint:disable-next-line:variable-name
const { name: _name, ...others } = value[n];
return transformItem(n, {
...others,
type: {
name: _name,
},
});
});
}
} else if (value !== undefined) {
result.value = value;
}
return result;
}
function transformItem(name: string, item: any): any {
const { description, type, required, defaultValue } = item;
const result: any = {
name,
propType: transformType({
...type,
required: !!required,
}),
};
if (description) {
result.description = description;
}
if (defaultValue) {
try {
const value = eval(defaultValue.value);
result.defaultValue = value;
} catch (e) {}
}
return result;
}
/**
* 解析 react 生态下的组件
*
* @class ReactParser
* @extends {BaseParser}
*/
class ReactParser extends BaseParser {
/**
* 解析 ExportStatement
* @static
* @returns {Promise<any>}
* @memberof ReactParser
*/
public static async parseExportedStatement(
fileContent: string,
sourceType: string,
): Promise<
Array<{
localName: string;
exportedName: string;
source: string;
}>
> {
const ast = parser.parse(fileContent);
// @ts-ignore
ast.__src = fileContent;
const specifiers: any = [];
// 组装 localName 和 exportedName
traverse(ast, {
enter(path) {
if (t.isExportNamedDeclaration(path.node)) {
path.node.specifiers.forEach(spec => {
if (t.isExportSpecifier(spec)) {
const source = (path.node as t.ExportNamedDeclaration).source;
specifiers.push({
localName: spec.local.name,
exportedName: spec.exported.name,
source: t.isLiteral(source) ? (source as any).value : '',
});
}
});
}
},
});
// 组装 source
traverse(ast, {
enter(path) {
if (t.isImportDeclaration(path.node)) {
const source = path.node.source;
path.node.specifiers.forEach(spec => {
if (t.isImportDefaultSpecifier(spec)) {
const target = specifiers.find(
(inner: any) => inner.localName === spec.local.name,
);
if (target) {
target.source = source.value;
}
}
});
}
},
});
debug('specifiers', specifiers);
return specifiers;
}
public static parseProperties(objectPath: any): IPropTypes {
const results: IPropTypes = objectPath
.get('properties')
.map((p: any) =>
transformItem(
p.get('key').node.name,
ReactDocUtils.getPropType(p.get('value')),
),
);
return results;
}
public async parseES5(
model: IMaterialScanModel,
): Promise<IMaterialParsedModel> {
const parsedModel: IMaterialParsedModel = {
filePath: '',
defaultExportName: '',
componentNames: [],
importModules: [],
exportModules: [],
subModules: [],
propsTypes: [],
propsDefaults: [],
};
const mainEntryItem: any = model.modules.find(
item => item.filePath === model.mainEntry,
);
const result = reactDocs.parse(
mainEntryItem.fileContent,
resolver,
handlers,
);
const props = Object.keys(result.props || {}).map(name => {
return transformItem(name, result.props[name]);
});
return {
filePath: mainEntryItem.filePath,
// defaultExportName,
// subModules,
// propsTypes,
props,
} as any;
// log('mainEntryItem', mainEntryItem);
// const ast = parser.parse(mainEntryItem.file);
// @ts-ignore
// ast.__src = mainEntryItem.file;
// // 获取 defaultExportName
// traverse(ast, {
// enter(path) {
// if (t.isExpressionStatement(path.node)) {
// if (
// t.isAssignmentExpression(path.node.expression) &&
// t.isMemberExpression(path.node.expression.left) &&
// t.isIdentifier(path.node.expression.left.object) &&
// t.isIdentifier(path.node.expression.right) &&
// path.node.expression.left.object.name === 'exports' &&
// (path.node.expression.left.property.name === 'default' ||
// path.node.expression.left.property.value === 'default')
// ) {
// // 支持 export default Demo 写法
// const tempVarName = path.node.expression.right.name;
// let defaultExportName = '';
// traverse(ast, {
// enter(innerPath) {
// if (
// t.isVariableDeclaration(innerPath.node) &&
// Array.isArray(innerPath.node.declarations) &&
// innerPath.node.declarations.length &&
// t.isVariableDeclarator(innerPath.node.declarations[0]) &&
// t.isIdentifier(innerPath.node.declarations[0].id) &&
// innerPath.node.declarations[0].id.name === tempVarName &&
// t.isIdentifier(innerPath.node.declarations[0].init)
// ) {
// defaultExportName = innerPath.node.declarations[0].init.name;
// }
// },
// });
// parsedModel.defaultExportName = defaultExportName;
// log('isIdentifier defaultExportName', defaultExportName);
// }
// }
// },
// });
// traverse(ast, {
// enter(path) {
// // 获取 componentNames
// if (t.isVariableDeclaration(path.node)) {
// if (
// t.isVariableDeclarator(path.node.declarations) &&
// t.isIdentifier(path.node.declarations.init) &&
// t.isIdentifier(path.node.declarations.id)
// ) {
// const exportedName = path.node.declarations.init.name;
// const localName = path.node.declarations.id.name;
// log('isIdentifier componentNames', exportedName);
// parsedModel.componentNames.push({
// exportedName,
// localName,
// });
// }
// }
// // 获取 exportModules
// if (t.isExpressionStatement(path.node)) {
// // 对应 export function DemoFunc() {} 或 export { DemoFunc } 写法
// if (
// t.isAssignmentExpression(path.node.expression) &&
// t.isMemberExpression(path.node.expression.left) &&
// t.isIdentifier(path.node.expression.left.object) &&
// t.isIdentifier(path.node.expression.left.property) &&
// t.isIdentifier(path.node.expression.right) &&
// path.node.expression.left.object.name === 'exports'
// ) {
// const exportedName = path.node.expression.left.property.name;
// const localName = path.node.expression.right.name;
// parsedModel.exportModules.push({
// exportedName:
// exportedName === 'default'
// ? parsedModel.defaultExportName
// : exportedName,
// localName:
// exportedName === 'default'
// ? parsedModel.defaultExportName
// : localName,
// });
// }
// // 支持 export { default as DemoFunc } from './DemoFunc' 写法
// if (
// t.isCallExpression(path.node.expression) &&
// t.isMemberExpression(path.node.expression.callee) &&
// t.isIdentifier(path.node.expression.callee.object) &&
// t.isIdentifier(path.node.expression.callee.property) &&
// path.node.expression.callee.object.name === 'Object' &&
// path.node.expression.callee.property.name === 'defineProperty' &&
// Array.isArray(path.node.expression.arguments) &&
// t.isIdentifier(path.node.expression.arguments[0]) &&
// (path.node.expression.arguments[0] as t.Identifier).name ===
// 'exports' &&
// t.isLiteral(path.node.expression.arguments[1])
// ) {
// // 对应 export function DemoFunc() {} 或 export { DemoFunc } 写法
// const args = path.node.expression.arguments as any;
// const funcName = args[1].value;
// if (funcName !== '__esModule') {
// parsedModel.exportModules.push({
// exportedName: funcName,
// localName: funcName,
// });
// }
// }
// }
// // 获取 importModules
// if (
// t.isVariableDeclaration(path.node) &&
// Array.isArray(path.node.declarations) &&
// path.node.declarations.length
// ) {
// path.node.declarations.forEach(dec => {
// // 支持 import Demo from './demo' 写法
// if (
// t.isVariableDeclarator(dec) &&
// t.isIdentifier(dec.id) &&
// t.isCallExpression(dec.init) &&
// t.isIdentifier(dec.init.callee) &&
// ['_interopRequireWildcard', '_interopRequireDefault'].includes(
// dec.init.callee.name,
// ) &&
// // dec.init.callee.name === '_interopRequireWildcard' &&
// Array.isArray(dec.init.arguments) &&
// t.isCallExpression(dec.init.arguments[0]) &&
// t.isIdentifier(
// (dec.init.arguments[0] as t.CallExpression).callee,
// ) &&
// ((dec.init.arguments[0] as t.CallExpression)
// .callee as t.Identifier).name === 'require'
// ) {
// const localName = dec.id.name;
// const args = (dec.init.arguments[0] as t.CallExpression)
// .arguments as any;
// const source = args[0].value;
// parsedModel.importModules.push({
// importDefaultName: localName,
// localName,
// source,
// });
// }
// // 支持 import { Demo as Demo2 } from './demo' 写法
// if (
// t.isVariableDeclarator(dec) &&
// t.isIdentifier(dec.id) &&
// t.isCallExpression(dec.init) &&
// t.isIdentifier(dec.init.callee) &&
// dec.init.callee.name === 'require' &&
// Array.isArray(dec.init.arguments) &&
// t.isLiteral(dec.init.arguments[0])
// ) {
// const args = dec.init.arguments as any;
// const source = args[0].value;
// const importName = dec.id.name;
// const localName = dec.id.name;
// // 遍历查找出 importName 和 localName
// // ES5 本身并不支持按需加载,故 import 都是全量导入
// // 但如果使用了诸如babel-plugin-import 等插件,会自动更改编译之后的 ES5 代码
// parsedModel.importModules.push({
// importName,
// localName,
// source,
// });
// }
// });
// }
// // 获取 subModules
// if (
// t.isExpressionStatement(path.node) &&
// t.isAssignmentExpression(path.node.expression) &&
// t.isMemberExpression(path.node.expression.left)
// ) {
// if (
// t.isIdentifier(path.node.expression.left.object) &&
// path.node.expression.left.object.name ===
// parsedModel.defaultExportName
// ) {
// // 支持 SFC.SubDemo1 = SubDemo1; 写法
// if (t.isIdentifier(path.node.expression.right)) {
// parsedModel.subModules.push({
// objectName: [path.node.expression.left.object.name],
// propertyName: path.node.expression.left.property.name,
// isValueAnonymousFunc: false,
// value: path.node.expression.right.name,
// });
// }
// // 支持 SFC.SubDemo2 = function() {}; 写法
// if (t.isFunctionExpression(path.node.expression.right)) {
// const rightID = path.node.expression.right.id as any;
// parsedModel.subModules.push({
// objectName: [path.node.expression.left.object.name],
// propertyName: path.node.expression.left.property.name,
// isValueAnonymousFunc: !rightID,
// value: rightID ? rightID.name : undefined,
// });
// }
// }
// if (t.isMemberExpression(path.node.expression.left.object)) {
// if (t.isIdentifier(path.node.expression.right)) {
// // 支持 DemoFunc4.Test.Obj2 = Obj3; 写法
// const tempLeftObject = path.node.expression.left.object as any;
// parsedModel.subModules.push({
// objectName: [
// tempLeftObject.object.name,
// tempLeftObject.property.name,
// ],
// propertyName: path.node.expression.left.property.name,
// isValueAnonymousFunc: false,
// value: path.node.expression.right.name,
// });
// }
// if (t.isFunctionExpression(path.node.expression.right)) {
// // 支持 DemoFunc4.Test.Obj2 = function() {}; 写法
// const rightID = path.node.expression.right.id as any;
// const tempLeftObject = path.node.expression.left.object as any;
// parsedModel.subModules.push({
// objectName: [
// tempLeftObject.object.name,
// tempLeftObject.property.name,
// ],
// propertyName: path.node.expression.left.property.name,
// isValueAnonymousFunc: !rightID,
// value: rightID ? rightID.name : undefined,
// });
// }
// }
// }
// // 获取 propsTypes 和 defaultProps
// if (
// t.isExpressionStatement(path.node) &&
// t.isAssignmentExpression(path.node.expression) &&
// t.isMemberExpression(path.node.expression.left) &&
// t.isObjectExpression(path.node.expression.right) &&
// t.isIdentifier(path.node.expression.left.object) &&
// t.isIdentifier(path.node.expression.left.property) &&
// path.node.expression.left.object.name ===
// parsedModel.defaultExportName &&
// ['propTypes', 'defaultProps'].includes(
// path.node.expression.left.property.name,
// )
// ) {
// // 处理 propTypes
// if (path.node.expression.left.property.name === 'propTypes') {
// path.node.expression.right.properties.forEach(prop => {
// if (t.isProperty(prop)) {
// if (t.isMemberExpression(prop.value)) {
// if (t.isIdentifier(prop.value.object)) {
// // 支持 optionalArray: PropTypes.array 写法
// parsedModel.propsTypes.push({
// name: prop.key.name,
// type: prop.value.property.name,
// required: false,
// });
// }
// if (t.isMemberExpression(prop.value.object)) {
// // 支持 optionalArray: PropTypes.array.isRequired 写法
// parsedModel.propsTypes.push({
// name: prop.key.name,
// type: prop.value.object.property.name,
// required:
// prop.value.object.property.name === 'isRequired',
// });
// }
// if (
// t.isCallExpression(prop.value.object) &&
// t.isMemberExpression(prop.value.object.callee)
// ) {
// // 支持 optionalArray: PropTypes.shape().isRequired 写法
// parsedModel.propsTypes.push({
// name: prop.key.name,
// type: prop.value.object.callee.property.name,
// required: prop.value.property.name === 'isRequired',
// });
// }
// }
// if (
// t.isCallExpression(prop.value) &&
// t.isMemberExpression(prop.value.callee)
// ) {
// // 支持 optionalArray: PropTypes.shape() 写法
// parsedModel.propsTypes.push({
// name: prop.key.name,
// type: prop.value.callee.property.name,
// required: false,
// });
// }
// }
// });
// }
// // 处理 defaultProps
// if (path.node.expression.left.property.name === 'defaultProps') {
// path.node.expression.right.properties.forEach(prop => {
// if (t.isProperty(prop)) {
// if (t.isObjectExpression(prop.value)) {
// const defaultValue = new CodeGenerator(
// t.objectExpression(prop.value.properties),
// ).generate().code;
// parsedModel.propsDefaults.push({
// name: prop.key.name,
// defaultValue,
// });
// }
// }
// });
// }
// }
// },
// });
// log('traverse done.');
// log('parsedModel.defaultExportName', parsedModel.defaultExportName);
// log('parsedModel.componentNames', parsedModel.componentNames);
// log('parsedModel.importModules', parsedModel.importModules);
// log('parsedModel.exportModules', parsedModel.exportModules);
// log('parsedModel.subModules', parsedModel.subModules);
// log('parsedModel.propsTypes', parsedModel.propsTypes);
// log('parsedModel.propsDefaults', parsedModel.propsDefaults);
// log('parsedModel', parsedModel);
// return parsedModel;
}
public async parseES6(params: {
model: IMaterialScanModel;
filePath: string;
fileContent: string;
}): Promise<IMaterialParsedModel> {
const ast = parser.parse(params.fileContent);
const result = reactDocs.parse(params.fileContent, resolver, handlers);
const props = Object.keys(result.props || {}).map(name => {
return transformItem(name, result.props[name]);
});
const defaultExportName = await this.parseDefaultExportNameES6(ast);
// const subModules = await this.parseSubModulesES6(ast);
return {
filePath: params.filePath,
defaultExportName,
// subModules,
props,
} as any;
}
/**
* 解析 AST 获取 defaultExportName
* 支持的写法:
* - export default Demo
* - export default function Demo() {}
* - export default class Demo {}
*
* @private
* @param {*} ast
* @memberof ReactParser
*/
private async parseDefaultExportNameES6(ast: any): Promise<string> {
let defaultExportName = '';
traverse(ast, {
enter(path) {
// 获取 defaultExportName
if (t.isExportDefaultDeclaration(path.node)) {
if (t.isIdentifier(path.node.declaration)) {
// 支持 export default Demo 写法
defaultExportName = path.node.declaration.name;
log('isIdentifier defaultExportName', defaultExportName);
}
if (t.isFunctionDeclaration(path.node.declaration)) {
if (t.isIdentifier(path.node.declaration.id)) {
// 支持 export default function Demo() {} 写法
defaultExportName = path.node.declaration.id.name;
log('isFunctionDeclaration defaultExportName', defaultExportName);
}
}
if (t.isClassDeclaration(path.node.declaration)) {
if (t.isIdentifier(path.node.declaration.id)) {
// 支持 export default class Demo {} 写法
defaultExportName = path.node.declaration.id.name;
log('isClassDeclaration defaultExportName', defaultExportName);
}
}
if (t.isCallExpression(path.node.declaration)) {
const traverseCallExp: any = (args: any[]) => {
const arg = args[0];
if (t.isIdentifier(arg)) {
return arg.name;
}
return traverseCallExp(arg.arguments);
};
defaultExportName = traverseCallExp(
path.node.declaration.arguments,
);
}
}
},
});
return defaultExportName;
}
/**
* 解析 AST 获取 subModules
* 支持的写法:
* - DemoFunc4.Test = Test;
* - DemoFunc4.Test = function() {};
* - DemoFunc4.Test.Obj2 = Obj3;
* - DemoFunc4.Test.Obj2 = function() {};
*
* @private
* @param {*} ast
* @returns {Promise<Array<{
* objectName: string[],
* propertyName: string,
* value?: string,
* isValueAnonymousFunc: boolean,
* }>>}
* @memberof ReactParser
*/
private async parseSubModulesES6(
ast: any,
): Promise<
Array<{
objectName: string[];
propertyName: string;
value?: string;
isValueAnonymousFunc: boolean;
}>
> {
const results: any[] = [];
traverse(ast, {
enter(path) {
if (t.isExpressionStatement(path.node)) {
if (t.isAssignmentExpression(path.node.expression)) {
if (t.isMemberExpression(path.node.expression.left)) {
if (t.isIdentifier(path.node.expression.left.object)) {
if (t.isIdentifier(path.node.expression.right)) {
// 支持 DemoFunc4.Test = Test; 写法
results.push({
objectName: [path.node.expression.left.object.name],
propertyName: path.node.expression.left.property.name,
isValueAnonymousFunc: false,
value: path.node.expression.right.name,
});
}
if (t.isFunctionExpression(path.node.expression.right)) {
// 支持 DemoFunc4.Test = function() {}; 写法
const rightID = !path.node.expression.right.id as any;
results.push({
objectName: [path.node.expression.left.object.name],
propertyName: path.node.expression.left.property.name,
isValueAnonymousFunc: !!rightID,
value: rightID ? rightID.name : undefined,
});
}
}
if (t.isMemberExpression(path.node.expression.left.object)) {
if (t.isIdentifier(path.node.expression.right)) {
// 支持 DemoFunc4.Test.Obj2 = Obj3; 写法
const tempLeftObject = path.node.expression.left
.object as any;
results.push({
objectName: [
tempLeftObject.object.name,
tempLeftObject.property.name,
],
propertyName: path.node.expression.left.property.name,
isValueAnonymousFunc: false,
value: path.node.expression.right.name,
});
}
if (t.isFunctionExpression(path.node.expression.right)) {
// 支持 DemoFunc4.Test.Obj2 = function() {}; 写法
const rightID = !path.node.expression.right.id as any;
const tempLeftObject = path.node.expression.left
.object as any;
results.push({
objectName: [
tempLeftObject.object.name,
tempLeftObject.property.name,
],
propertyName: path.node.expression.left.property.name,
isValueAnonymousFunc: !!rightID,
value: rightID ? rightID.name : undefined,
});
}
}
}
}
}
},
});
return results;
}
}
export default ReactParser;