import fs from 'fs' import { parse } from '@babel/parser' import * as types from '@babel/types' import traverse, { NodePath, Node } from '@babel/traverse' import { ComponentInfo } from './type' import Path from 'path' /** * Given an import source, return the file path * @param source * @returns file path */ const filePath = (source: string): string | null => { if (fs.existsSync(`${source}.tsx`)) { return `${source}.tsx` } if (fs.existsSync(`${source}.jsx`)) { return `${source}.jsx` } return null } /** * Given a file path, parse the file code into an AST * @param path * @returns */ export const parseFile = (path: string): { code: string, ast: Node } => { const code = fs.readFileSync(path).toString() return { code, ast: parse( code, { sourceType: 'module', plugins: [ 'jsx', 'typescript' ] } ) } } export const isTemplate = (ast: Node): boolean => { let isValid = false traverse(ast, { ExportDefaultDeclaration (path) { const declaration = path.get('declaration') if (!declaration.isCallExpression()) { return } const callee = declaration.get('callee') if (!types.isIdentifier(callee.node) || callee.node.name !== 'Template') { return } const binding = callee.scope.getBinding('Template') if ((binding == null) || !binding.path.isImportSpecifier() || !binding.path.parentPath.isImportDeclaration() || binding.path.parentPath.get('source').node.value !== 'react-json-templates' ) { return } isValid = true } }) return isValid } export const getSerializableName = (ast: Node): string | null => { let name: string | null = null traverse(ast, { ExportDefaultDeclaration (path) { const declaration = path.get('declaration') if (!declaration.isCallExpression()) { return } const callee = declaration.get('callee') const namePath = declaration.get('arguments')[0] if ( !types.isIdentifier(callee.node) || callee.node.name !== 'Serializable' || !namePath.isStringLiteral() ) { return } const binding = callee.scope.getBinding('Serializable') if ((binding == null) || !binding.path.isImportSpecifier() || !binding.path.parentPath.isImportDeclaration() || binding.path.parentPath.get('source').node.value !== 'react-json-templates' ) { return } name = namePath.node.value } }) return name } /** * Given a file path, parse the file code and return the component informations * @param path * @returns */ export const parseComponent = (path: string): ComponentInfo => { const { ast } = parseFile(path) let info: ComponentInfo = null traverse(ast, { ExportDefaultDeclaration () { // Template if (isTemplate(ast)) { info = { type: 'Template' } return } // Serializable const serializableName = getSerializableName(ast) if (serializableName != null) { info = { type: 'Serializable', name: serializableName } } } }) return info } /** * Given an import sepcifier node path, resolve and parse the import source, and return the module component informations * @param path * @returns */ export const resolveComponent = ( path: NodePath, baseDirectory: string ): ComponentInfo => { if (!path.parentPath.isImportDeclaration()) { return null } const source = Path.join(baseDirectory, path.parentPath.node.source.value) const file = filePath(source) if (file == null) { return null } return parseComponent(file) } export const buildProps = ( attributes: Array, children: types.Expression[] ): types.ObjectExpression => { const convertAttribute = ( node: types.JSXAttribute | types.JSXSpreadAttribute ): types.SpreadElement | types.ObjectProperty => { if (types.isJSXSpreadAttribute(node)) { return types.spreadElement(node.argument) } const propertyName = types.identifier(node.name.name as string) if (node.value == null) { return types.objectProperty( propertyName, types.booleanLiteral(true) ) } if (types.isJSXExpressionContainer(node.value)) { return types.objectProperty( propertyName, types.isExpression(node.value.expression) ? node.value.expression : types.nullLiteral() ) } return types.objectProperty( propertyName, node.value ) } const props = attributes.map(convertAttribute) return types.objectExpression([ ...props, types.objectProperty( types.identifier('children'), types.arrayExpression(children) ) ]) }