import type { API, ArrowFunctionExpression, ASTPath, ExportDefaultDeclaration, FileInfo, FunctionDeclaration, Options, } from 'jscodeshift' import { basename, extname } from 'path' const camelCase = (value: string): string => { const val = value.replace(/[-_\s.]+(.)?/g, (_match, chr) => chr ? chr.toUpperCase() : '' ) return val.slice(0, 1).toUpperCase() + val.slice(1) } const isValidIdentifier = (value: string): boolean => /^[a-zA-ZÀ-ÿ][0-9a-zA-ZÀ-ÿ]+$/.test(value) export default function transformer( file: FileInfo, api: API, options: Options ) { const j = api.jscodeshift.withParser('tsx') const root = j(file.source) let hasModifications: boolean const returnsJSX = (node): boolean => node.type === 'JSXElement' || (node.type === 'BlockStatement' && j(node) .find(j.ReturnStatement) .some((path) => path.value.argument?.type === 'JSXElement')) const hasRootAsParent = (path): boolean => { const program = path.parentPath.parentPath.parentPath.parentPath.parentPath return !program || program?.value?.type === 'Program' } const nameFunctionComponent = ( path: ASTPath ): void => { const node = path.value if (!node.declaration) { return } const isArrowFunction = node.declaration.type === 'ArrowFunctionExpression' && returnsJSX(node.declaration.body) const isAnonymousFunction = node.declaration.type === 'FunctionDeclaration' && !node.declaration.id if (!(isArrowFunction || isAnonymousFunction)) { return } const fileName = basename(file.path, extname(file.path)) let name = camelCase(fileName) // If the generated name looks off, don't add a name if (!isValidIdentifier(name)) { return } // Add `Component` to the end of the name if an identifier with the // same name already exists while (root.find(j.Identifier, { name }).some(hasRootAsParent)) { // If the name is still duplicated then don't add a name if (name.endsWith('Component')) { return } name += 'Component' } hasModifications = true if (isArrowFunction) { path.insertBefore( j.variableDeclaration('const', [ j.variableDeclarator( j.identifier(name), node.declaration as ArrowFunctionExpression ), ]) ) node.declaration = j.identifier(name) } else { // Anonymous Function ;(node.declaration as FunctionDeclaration).id = j.identifier(name) } } root.find(j.ExportDefaultDeclaration).forEach(nameFunctionComponent) return hasModifications ? root.toSource(options) : null }