diff --git a/packages/code-generator/src/plugins/component/rax/jsx.ts b/packages/code-generator/src/plugins/component/rax/jsx.ts index eb6c513af..a65ebba4e 100644 --- a/packages/code-generator/src/plugins/component/rax/jsx.ts +++ b/packages/code-generator/src/plugins/component/rax/jsx.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import changeCase from 'change-case'; +import { Expression, MemberExpression } from '@babel/types'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, @@ -25,7 +26,11 @@ import { generateExpression } from '../../../utils/jsExpression'; import { CustomHandlerSet, generateUnknownType } from '../../../utils/compositeType'; import { IScopeBindings, ScopeBindings } from '../../../utils/ScopeBindings'; -import { parseExpressionConvertThis2Context, parseExpressionGetGlobalVariables } from '../../../utils/expressionParser'; +import { + parseExpression, + parseExpressionConvertThis2Context, + parseExpressionGetGlobalVariables, +} from '../../../utils/expressionParser'; type PluginConfig = { fileType: string; @@ -181,7 +186,62 @@ function transformLoopExpr(expr: string, handlers: CustomHandlerSet) { } function transformJsExpr(expr: string, handlers: CustomHandlerSet) { - return isLiteralAtomicExpr(expr) ? expr : `__$$eval(() => (${transformThis2Context(expr, handlers)}))`; + if (!expr) { + return 'undefined'; + } + + if (isLiteralAtomicExpr(expr)) { + return expr; + } + + const exprAst = parseExpression(expr); + switch (exprAst.type) { + // 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层 + case 'BigIntLiteral': + case 'BooleanLiteral': + case 'DecimalLiteral': + case 'NullLiteral': + case 'NumericLiteral': + case 'RegExpLiteral': + case 'StringLiteral': + return expr; + + // 对于直接写个函数的,则不用再包下,因为这样不会抛出异常的 + case 'ArrowFunctionExpression': + case 'FunctionExpression': + return transformThis2Context(exprAst, handlers); + + // 对于直接访问 this.xxx, this.utils.xxx, this.state.xxx 的也不用再包下 + case 'MemberExpression': + if (isSimpleDirectlyAccessingThis(exprAst) || isSimpleDirectlyAccessingSafeProperties(exprAst)) { + return transformThis2Context(exprAst, handlers); + } + + break; + + default: + break; + } + + // 其他的都需要包一层 + return `__$$eval(() => (${transformThis2Context(exprAst, handlers)}))`; +} + +/** this.xxx */ +function isSimpleDirectlyAccessingThis(exprAst: MemberExpression) { + return !exprAst.computed && exprAst.object.type === 'ThisExpression'; +} + +/** this.state.xxx 和 this.utils.xxx 等安全的肯定应该存在的东东 */ +function isSimpleDirectlyAccessingSafeProperties(exprAst: MemberExpression): boolean { + return ( + !exprAst.computed && + exprAst.object.type === 'MemberExpression' && + exprAst.object.object.type === 'ThisExpression' && + !exprAst.object.computed && + exprAst.object.property.type === 'Identifier' && + /^(state|utils|constants|i18n)$/.test(exprAst.object.property.name) + ); } function isImportAliasDefineChunk( @@ -206,14 +266,14 @@ function isImportAliasDefineChunk( * 判断是否是原子类型的表达式 */ function isLiteralAtomicExpr(expr: string): boolean { - return expr === 'null' || expr === 'undefined' || expr === 'true' || expr === 'false' || /^\d+$/.test(expr); + return expr === 'null' || expr === 'undefined' || expr === 'true' || expr === 'false' || /^-?\d+(\.\d+)?$/.test(expr); } /** * 将所有的 this.xxx 替换为 __$$context.xxx * @param expr */ -function transformThis2Context(expr: string, customHandlers: CustomHandlerSet): string { +function transformThis2Context(expr: string | Expression, customHandlers: CustomHandlerSet): string { // return expr // .replace(/\bthis\.item\./g, () => 'item.') // .replace(/\bthis\.index\./g, () => 'index.') diff --git a/packages/code-generator/src/utils/expressionParser.ts b/packages/code-generator/src/utils/expressionParser.ts index 0b7755171..7a932f1b4 100644 --- a/packages/code-generator/src/utils/expressionParser.ts +++ b/packages/code-generator/src/utils/expressionParser.ts @@ -7,8 +7,8 @@ import { isIdentifier, Node } from '@babel/types'; import { OrderedSet } from './OrderedSet'; export class ParseError extends Error { - constructor(public readonly expr: string, public readonly detail: unknown) { - super(`Failed to parse expression "${expr}"`); + constructor(public readonly expr: string | t.Expression, public readonly detail: unknown) { + super(`Failed to parse expression "${typeof expr === 'string' ? expr : generate(expr)}"`); Object.setPrototypeOf(this, new.target.prototype); } } @@ -159,7 +159,7 @@ export function parseExpressionGetGlobalVariables( } export function parseExpressionConvertThis2Context( - expr: string, + expr: string | t.Expression, contextName: string = '__$$context', localVariables: string[] = [], ): string { @@ -168,7 +168,7 @@ export function parseExpressionConvertThis2Context( } try { - const exprAst = parser.parseExpression(expr); + const exprAst = typeof expr === 'string' ? parser.parseExpression(expr) : expr; const exprWrapAst = t.expressionStatement(exprAst); const fileAst = t.file(t.program([exprWrapAst])); @@ -229,11 +229,14 @@ export function parseExpressionConvertThis2Context( const { code } = generate(exprWrapAst.expression, { sourceMaps: false }); return code; } catch (e) { - // throw new ParseError(expr, e); - throw e; + throw new ParseError(expr, e); } } -function indent(level: number) { - return ' '.repeat(level); +export function parseExpression(expr: string) { + try { + return parser.parseExpression(expr); + } catch (e) { + throw new ParseError(expr, e); + } } diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx index b1b50c437..f506deab2 100644 --- a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx @@ -79,7 +79,7 @@ class Home$$Page extends Component { === User Info: === - {__$$eval(() => __$$context.state.user) && ( + {__$$context.state.user && ( __$$context.state.user.avatar) }} @@ -128,7 +128,7 @@ class Home$$Page extends Component { }); }} > - 点击次数:{__$$eval(() => __$$context.state.clickCount)}(点击加 1) + 点击次数:{__$$context.state.clickCount}(点击加 1) 操作提示: diff --git a/packages/code-generator/test-cases/rax-app/demo2/schema.json5 b/packages/code-generator/test-cases/rax-app/demo2/schema.json5 index 96818e695..336e37b29 100644 --- a/packages/code-generator/test-cases/rax-app/demo2/schema.json5 +++ b/packages/code-generator/test-cases/rax-app/demo2/schema.json5 @@ -87,7 +87,7 @@ isSync: true, }, dataHandler: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (response) {\nif (!response.success){\n throw new Error(response.message);\n }\n return response.data;\n}', }, }, @@ -104,13 +104,13 @@ isSync: true, }, dataHandler: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (response) {\nif (!response.success){\n throw new Error(response.message);\n }\n return response.data.result;\n}', }, }, ], dataHandler: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}', }, }, @@ -164,7 +164,7 @@ componentName: 'View', props: { onClick: { - type: 'JSFunction', + type: 'JSExpression', value: 'this.hello', }, }, @@ -210,7 +210,7 @@ props: { style: { flexDirection: 'row' }, onClick: { - type: 'JSFunction', + type: 'JSExpression', value: 'function(){ this.utils.recordEvent(`CLICK_ORDER`, this.order.title) }', }, }, @@ -260,7 +260,7 @@ componentName: 'View', props: { onClick: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (){ this.setState({ clickCount: this.state.clickCount + 1 }) }', }, }, @@ -312,7 +312,7 @@ name: 'formatPrice', type: 'function', content: { - type: 'JSFunction', + type: 'JSExpression', value: 'function formatPrice(price, unit) { return Number(price).toFixed(2) + unit; }', }, }, @@ -321,7 +321,7 @@ name: 'recordEvent', type: 'function', content: { - type: 'JSFunction', + type: 'JSExpression', value: 'function recordEvent(eventName, eventDetail) { \n this.utils.Toast.show(`[EVENT]: ${eventName} ${eventDetail}`);\n console.log(`[EVENT]: ${eventName} (detail: %o) (user: %o)`, eventDetail, this.state.user); }', }, },