diff --git a/src/basetran/handleMisc.js b/src/basetran/handleMisc.js index a318326..5f7e398 100644 --- a/src/basetran/handleMisc.js +++ b/src/basetran/handleMisc.js @@ -35,12 +35,9 @@ export default function (ast, {isFuncComp}) { * const x = 1 * export default x * - * 函数式组件在后续会被处理为class组件,这里不需要处理 - * */ - if (!isFuncComp - && path.type === 'ExportDefaultDeclaration' + if (path.type === 'ExportDefaultDeclaration' && path.node.declaration && path.node.declaration.type === 'AssignmentExpression' && path.node.declaration.left.type === 'Identifier' diff --git a/src/tran/funcCompToClassComp.js b/src/tran/funcCompToClassComp.js index 35988ad..7477dcf 100644 --- a/src/tran/funcCompToClassComp.js +++ b/src/tran/funcCompToClassComp.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * */ - + import traverse from "@babel/traverse"; import * as t from "@babel/types"; import {miscNameToJSName} from '../util/util' @@ -14,7 +14,7 @@ const npath = require('path') /** - * 把函数声明的组件, 转化为class + * 把函数声明的组件, 转化为FuncComponent 组件 * * 1. export default () => {} * @@ -33,96 +33,141 @@ const npath = require('path') * * * @param ast - * @param info * @returns {*} */ -export default function (ast, info) { - let compName = npath.basename(miscNameToJSName(info.filepath), '.js') - compName = compName.substring(0, 1).toUpperCase() + compName.substring(1) +export default function funcCompToClassComp(ast, info) { + + let funcPath = null + let hasJSXElement = false traverse(ast, { + enter: path => { + // function x {} + if (path.type === 'FunctionDeclaration' && path.parentPath.type === 'Program') { + funcPath = path + hasJSXElement = false + } + + // const x = () => {} + if (path.type === 'ArrowFunctionExpression' + && path.parentPath.type === 'VariableDeclarator' + && path.parentPath.parentPath.parentPath.type === 'Program' + ) { + funcPath = path + hasJSXElement = false + } + + // export const x = () => {} + if (path.type === 'ArrowFunctionExpression' + && path.parentPath.type === 'VariableDeclarator' + && path.parentPath.parentPath.parentPath.type === 'ExportNamedDeclaration' + ) { + funcPath = path + hasJSXElement = false + } + + // const b = function (){} + if (path.type === 'FunctionExpression' + && path.parentPath.type === 'VariableDeclarator' + && path.parentPath.parentPath.parentPath.type === 'Program' + ) { + funcPath = path + hasJSXElement = false + } + + // export const b = function (){} + if (path.type === 'FunctionExpression' + && path.parentPath.type === 'VariableDeclarator' + && path.parentPath.parentPath.parentPath.type === 'ExportNamedDeclaration' + ) { + funcPath = path + hasJSXElement = false + } + + // export default () => {} + if (path.type === 'ArrowFunctionExpression' + && path.parentPath.type === 'ExportDefaultDeclaration' + ) { + funcPath = path + hasJSXElement = false + } + + // export default function () {} + if (path.type === 'FunctionDeclaration' + && path.parentPath.type === 'ExportDefaultDeclaration' + ) { + funcPath = path + hasJSXElement = false + } + + // export default (function () {}) + if (path.type === 'FunctionExpression' + && path.parentPath.type === 'ExportDefaultDeclaration' + ) { + funcPath = path + hasJSXElement = false + } + + if (path.type === 'JSXOpeningElement') { + hasJSXElement = true + } + }, + exit: path => { - if (path.type === 'ExportDefaultDeclaration') { - const declaration = path.node.declaration + if (path.type === 'ClassDeclaration' || path.type === 'ClassExpression') { + // 下面的path.replaceWith 将导致exit 在执行一次 + return + } + + if (path === funcPath && hasJSXElement) { + const funcPathNode = funcPath.node + + if (funcPathNode.body.type !== 'BlockStatement') { + funcPathNode.body = t.blockStatement([ + t.returnStatement(funcPathNode.body) + ]) + } - let renderBody = getRenderBody(declaration) - path.node.declaration = t.classDeclaration( - t.identifier(compName), + const propsVar = funcPathNode.params[0] + if (propsVar) { + const propDec = t.variableDeclaration('const', [ + t.variableDeclarator(propsVar, t.memberExpression(t.thisExpression(), t.identifier('props'))) + ]) + + funcPathNode.body.body.unshift(propDec) + } + + const contextVar = funcPathNode.params[1] + if (contextVar) { + const contextDec = t.variableDeclaration('const', [ + t.variableDeclarator(contextVar, t.memberExpression(t.thisExpression(), t.identifier('context'))) + ]) + + funcPathNode.body.body.unshift(contextDec) + } + + + const classDec = t.classExpression( + path.node.id, t.memberExpression(t.identifier('React'), t.identifier('FuncComponent')), t.classBody([ t.classMethod( 'method', t.identifier('render'), [], - t.blockStatement(renderBody) + funcPathNode.body ) ]) ) + + if (funcPathNode.type === 'FunctionDeclaration') { + classDec.type = 'ClassDeclaration' + } + + path.replaceWith(classDec) } } }) return ast } - - -function getRenderBody(declaration) { - let renderBody = null - let params = null - if (declaration.type === 'AssignmentExpression') { - const { operator, right} = declaration - if (operator === '=' && (right.type === 'ArrowFunctionExpression' || right.type === 'FunctionExpression')) { - - params = right.params - - if (right.body.body) { - renderBody = [...right.body.body] - } else { - renderBody = [ - t.returnStatement(right.body) - ] - } - } - } - - if (declaration.type === 'ArrowFunctionExpression') { - const func = declaration - params = func.params - if (func.body.body) { - renderBody = [...func.body.body] - } else { - renderBody = [ - t.returnStatement(func.body) - ] - } - } - - if (declaration.type === 'FunctionDeclaration') { - const func = declaration - params = func.params - renderBody = [ - ...func.body.body - ] - } - - - const propsVar = params[0] - if (propsVar) { - const propDec = t.variableDeclaration('const', [ - t.variableDeclarator(propsVar, t.memberExpression(t.thisExpression(), t.identifier('props'))) - ]) - - renderBody.unshift(propDec) - } - - const contextVar = params[1] - if (contextVar) { - const contextDec = t.variableDeclaration('const', [ - t.variableDeclarator(contextVar, t.memberExpression(t.thisExpression(), t.identifier('context'))) - ]) - - renderBody.unshift(contextDec) - } - - return renderBody -} \ No newline at end of file diff --git a/test/help/utils.js b/test/help/utils.js index 4663f4d..77f190b 100644 --- a/test/help/utils.js +++ b/test/help/utils.js @@ -6,7 +6,7 @@ * */ -import {parseCode, geneCode} from '../../src/util/uast' +import {parseCode, geneJSXCode} from '../../src/util/uast' /** * 通过trans把code转化为newCode @@ -17,13 +17,13 @@ import {parseCode, geneCode} from '../../src/util/uast' */ export function getNewCode(code, info, ...trans) { - let ast = parseCode(code) + let ast = parseCode(code, '.js') for(let i = 0; i < trans.length; i ++) { const tranFunc = trans[i] ast = tranFunc.call(null, ast, info) } - return geneCode(ast) + return geneJSXCode(ast) } export function expectNewCode(code, expectCode, info, ...trans) { diff --git a/test/tran/funcCompToClassComp.test.js b/test/tran/funcCompToClassComp.test.js index c6e3a3a..1a1f59b 100644 --- a/test/tran/funcCompToClassComp.test.js +++ b/test/tran/funcCompToClassComp.test.js @@ -21,54 +21,137 @@ describe('function component to class component', () => { } }) - it('箭头函数组件转化为类组件声明', () => { + it('声明变量,变量是函数组件', () => { const code = ` - export default () => + const f = (props) => + + const h = function() { + return + } + + const v = function v() { + return + } + + const g = (props) => { + return + } ` const expectCode = ` - export default class MyFunc extends React.FuncComponent { + const f = class extends React.FuncComponent { render() { - return ; + const props = this.props + return + } + } + const h = class extends React.FuncComponent { + render() { + return + } + } + const v = class v extends React.FuncComponent { + render() { + return + } + } + const g = class extends React.FuncComponent { + render() { + const props = this.props + return } + } + ` + expectNewCode(code, expectCode, {filepath: '/a/b/MyFunc.js'}, funcCompToClassComp) + }) + + + it('导出变量,变量是函数组件', () => { + const code = ` + export const f = (props) => + + export const h = function() { + return + } + + export const v = function v() { + return + } + export const g = (props) => { + return + } + ` + const expectCode = ` + export const f = class extends React.FuncComponent { + render() { + const props = this.props + return + } + } + export const h = class extends React.FuncComponent { + render() { + return + } + } + export const v = class v extends React.FuncComponent { + render() { + return + } + } + export const g = class extends React.FuncComponent { + render() { + const props = this.props + return + } } ` expectNewCode(code, expectCode, {filepath: '/a/b/MyFunc.js'}, funcCompToClassComp) }) - it('普通函数组件转化为类组件声明', () => { + + it('默认导出 箭头函数组件', () => { const code = ` - export default function(props) { - const {a, b} = props - return ( - - - {a} - {b} - - - ) - } + export default (props) => ` const expectCode = ` - export default class MyFunc extends React.FuncComponent { + export default (class extends React.FuncComponent { render() { const props = this.props - const { - a, - b - } = props; - return ( - - {a} - {b} - - ) + return + } + }) + ` + expectNewCode(code, expectCode, {filepath: '/a/b/MyFunc.js'}, funcCompToClassComp) + }) + + it('默认导出 函数组件', () => { + const code = ` + export default function h() {return } + ` + const expectCode = ` + export default class h extends React.FuncComponent { + render() { + return + } + } + ` + expectNewCode(code, expectCode, {filepath: '/a/b/MyFunc.js'}, funcCompToClassComp) + }) + + it('props, context 声明', () => { + const code = ` + export default function h({a, b}, {c, d}) {return } + ` + const expectCode = ` + export default class h extends React.FuncComponent { + render() { + const { c, d } = this.context + const { a, b } = this.props + return } - } - ` + ` expectNewCode(code, expectCode, {filepath: '/a/b/MyFunc.js'}, funcCompToClassComp) }) +}) -}) \ No newline at end of file