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); }',
},
},