react-diagram-schema
Version:
Parses React components from a file entry point and generates/writes a complete schema to a file
182 lines (162 loc) • 6.62 kB
JavaScript
// imports
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const isInlineReactComponent = require("./utils/isInlineReactComponent");
const isFunctionDefinedReactComponent = require("./utils/isFunctionDefinedReactComponent");
const extract_exportDeclarationPaths = require("./utils/extract_exportDeclarationPaths");
const extract_exportVariableDeclaratorPaths = require("./utils/extract_exportVariableDeclaratorPaths");
const extract_exportFunctionDeclarationPaths = require("./utils/extract_exportFunctionDeclarationPaths");
const extractMetadata = require("./utils/extractMetadata");
const handleProviders = require("./utils/handleProviders");
/* Build the following schema structure
*
* const components = [
* {
* name: ""
* internal: { states: [], functions: [], },
* external: { props: [], context: [], constants: [] },
* location: {line, filepath}
* }
* ];
*
*/
/* A TEST-COMPONENT FOR DEBUGGING CODE PARSER!!
const code = `function MyComponent({ children, propA, propB, propC }) {
const [count, setCount] = useState(0);
const [theme, setTheme] = React.useState("");
const favouriteColor = React.useContext(FavouriteColorContext);
const {theme1, theme2} = React.useContext(FavouriteTheme);
function A(){}
function B(){function C(){}}
return <div>{count}</div>;
}
`;
*/
function parseCode(code, filepath) {
// schema setup
const components = {};
// ast @babel/parser
const ast = parser.parse(code, {
plugins: ["jsx", "typescript"],
sourceType: "module",
});
// ast @babel/traverse
traverse(ast, {
// In every parsed file, the top-level node of the AST is always a program node
// There is only one program per file
Program(path) {
const program_bodyPath = path.get("body");
// ---- support-exported components -----------------------------------------------------------------
//EXTRACT export declarations
const exportDeclarationPaths =
extract_exportDeclarationPaths(program_bodyPath);
// ---- inline-defined components -----------------------------------------------------------------
//EXTRACT inline REACT-COMPONENTS
const inlineComponentDeclarationPaths = program_bodyPath.filter((p) =>
isInlineReactComponent(p),
);
const inlineComponentDeclaratorPaths =
inlineComponentDeclarationPaths.map(
(declaration) => declaration.get("declarations")[0],
);
//EXTRACT inline export declarations
const exportVariableDeclaratorPaths =
extract_exportVariableDeclaratorPaths(exportDeclarationPaths);
//EXTRACT provider
const varDeclarators = program_bodyPath
.filter((path) => path.isVariableDeclaration())
.map((decl) => decl.get("declarations")[0])
.filter((p) => p.isVariableDeclarator())
.filter((declarator) => declarator.get("init").isMemberExpression);
const exportVarDeclarators = exportDeclarationPaths
.filter((path) => path.get("declaration").isVariableDeclaration())
.map((p) => p.get("declaration").get("declarations")[0])
.filter((p) => p.isVariableDeclarator())
.filter((declarator) => declarator.get("init").isMemberExpression);
const providers = handleProviders(
[...varDeclarators, ...exportVarDeclarators],
filepath,
);
providers.forEach(
(provider) =>
(components[`${provider.name}::${provider.location.filepath}`] =
provider),
);
//MERGE exports WITH normal inline declarations
exportVariableDeclaratorPaths.forEach((exportVariable) =>
inlineComponentDeclaratorPaths.push(exportVariable),
);
//EXTRACT metadata { name: "", internal: {states: [], functions: []}, location: null } FROM EACH inline REACT COMPONENT
const metadata = extractMetadata(
inlineComponentDeclaratorPaths,
"inline",
code,
filepath,
);
//APPEND COMPONENT-LOGIC TO SCHEMA (inline React components)
metadata.forEach(
(obj) => (components[`${obj.name}::${obj.location.filepath}`] = obj),
);
// ---- function-defined components -----------------------------------------------------------------
//EXTRACT function-defined REACT COMPONENTS
const functionDefinedComponentPaths = program_bodyPath.filter((p) =>
isFunctionDefinedReactComponent(p),
);
//EXTRACT export named function declaration REACT COMPONENTS
const exportFunctionDeclarationPaths =
extract_exportFunctionDeclarationPaths(exportDeclarationPaths);
//EXTRACT export default declaration REACT COMPONENTS
const isDefaultExport_true = true;
const exportDefaultDeclarationPaths =
extract_exportFunctionDeclarationPaths(
exportDeclarationPaths,
isDefaultExport_true,
);
//MERGE named exports WITH normal function-defined declarations
exportFunctionDeclarationPaths.forEach((exportFunction) =>
functionDefinedComponentPaths.push(exportFunction),
);
//EXTRACT metadata FROM function-defined COMPONENTS
const metadataDefined = extractMetadata(
functionDefinedComponentPaths,
"defined",
code,
filepath,
);
//EXTRACT metadata FROM export default COMPONENTS
const metadataDefault = extractMetadata(
exportDefaultDeclarationPaths,
"defined",
code,
filepath,
isDefaultExport_true,
);
//MERGE export default declaration with named declarations
metadataDefault.forEach((f) => metadataDefined.push(f));
//APPEND COMPONENT-LOGIC TO SCHEMA
metadataDefined.forEach(
(obj) => (components[`${obj.name}::${obj.location.filepath}`] = obj),
);
//EXTRACT anonymous inline default export REACT COMPONENTS (e.g. export default () => <></>)
const exportDefaultAnonymousDeclarationPath = exportDeclarationPaths
.filter(
(path) =>
path.isExportDefaultDeclaration() &&
path.node.declaration?.type === "ArrowFunctionExpression",
)
.map((path) => path.get("declaration"));
const metadataAnonymous = extractMetadata(
exportDefaultAnonymousDeclarationPath,
"defined",
code,
filepath,
isDefaultExport_true,
);
metadataAnonymous.forEach(
(obj) => (components[`::${obj.location.filepath}`] = obj),
);
},
});
return components;
}
module.exports = parseCode;