gengyang 64c9daa1f4 refactor: 💡 refactor with react-docgen
BREAKING CHANGE: 🧨 use react-docgen to replace parser
2020-03-16 16:32:41 +08:00

158 lines
4.6 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import { namedTypes as t, visit } from 'ast-types';
const {
isExportsOrModuleAssignment,
isReactComponentClass,
isReactCreateClassCall,
isReactForwardRefCall,
isStatelessComponent,
normalizeClassDefinition,
resolveExportDeclaration,
resolveToValue,
} = require('react-docgen').utils;
import resolveHOC from './resolveHOC';
import resolveIIFE from './resolveIIFE';
import resolveTranspiledClass from './resolveTranspiledClass';
const ERROR_MULTIPLE_DEFINITIONS =
'Multiple exported component definitions found.';
function ignore() {
return false;
}
function isComponentDefinition(path: any) {
return (
isReactCreateClassCall(path) ||
isReactComponentClass(path) ||
isStatelessComponent(path) ||
isReactForwardRefCall(path)
);
}
function resolveDefinition(definition: any) {
if (isReactCreateClassCall(definition)) {
// return argument
const resolvedPath = resolveToValue(definition.get('arguments', 0));
if (t.ObjectExpression.check(resolvedPath.node)) {
return resolvedPath;
}
} else if (isReactComponentClass(definition)) {
normalizeClassDefinition(definition);
return definition;
} else if (
isStatelessComponent(definition) ||
isReactForwardRefCall(definition)
) {
return definition;
}
return null;
}
/**
* Given an AST, this function tries to find the exported component definition.
*
* The component definition is either the ObjectExpression passed to
* `React.createClass` or a `class` definition extending `React.Component` or
* having a `render()` method.
*
* If a definition is part of the following statements, it is considered to be
* exported:
*
* modules.exports = Definition;
* exports.foo = Definition;
* export default Definition;
* export var Definition = ...;
*/
export default function findExportedComponentDefinition(ast: any) {
let foundDefinition: any;
function exportDeclaration(path: any) {
const definitions = resolveExportDeclaration(path).reduce(
(acc: any, definition: any) => {
if (isComponentDefinition(definition)) {
acc.push(definition);
} else {
definition = resolveToValue(resolveIIFE(definition));
if (!isComponentDefinition(definition)) {
definition = resolveTranspiledClass(definition);
// console.log("path");
}
const resolved = resolveToValue(resolveHOC(definition));
if (isComponentDefinition(resolved)) {
acc.push(resolved);
}
}
return acc;
},
[],
);
if (definitions.length === 0) {
return false;
}
if (definitions.length > 1 || foundDefinition) {
// If a file exports multiple components, ... complain!
throw new Error(ERROR_MULTIPLE_DEFINITIONS);
}
foundDefinition = resolveDefinition(definitions[0]);
return false;
}
visit(ast, {
visitFunctionDeclaration: ignore,
visitFunctionExpression: ignore,
visitClassDeclaration: ignore,
visitClassExpression: ignore,
visitIfStatement: ignore,
visitWithStatement: ignore,
visitSwitchStatement: ignore,
visitWhileStatement: ignore,
visitDoWhileStatement: ignore,
visitForStatement: ignore,
visitForInStatement: ignore,
visitForOfStatement: ignore,
visitImportDeclaration: ignore,
visitExportNamedDeclaration: exportDeclaration,
visitExportDefaultDeclaration: exportDeclaration,
visitAssignmentExpression(path: any) {
// Ignore anything that is not `exports.X = ...;` or
// `module.exports = ...;`
if (!isExportsOrModuleAssignment(path)) {
return false;
}
// Resolve the value of the right hand side. It should resolve to a call
// expression, something like React.createClass
path = resolveToValue(path.get('right'));
if (!isComponentDefinition(path)) {
// path = resolveToValue(resolveHOC(path));
// if (!isComponentDefinition(path)) {
path = resolveToValue(resolveIIFE(path));
if (!isComponentDefinition(path)) {
path = resolveTranspiledClass(path);
if (!isComponentDefinition(path)) {
path = resolveToValue(resolveHOC(path));
}
}
}
if (foundDefinition) {
// If a file exports multiple components, ... complain!
throw new Error(ERROR_MULTIPLE_DEFINITIONS);
}
foundDefinition = resolveDefinition(path);
return false;
},
});
return foundDefinition;
}