onelaraveljs 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/onejs-build.js +32 -0
- package/package.json +11 -3
- package/scripts/README-template-compiler.md +133 -0
- package/scripts/README.md +61 -0
- package/scripts/__pycache__/build.cpython-314.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
- package/scripts/build.py +573 -0
- package/scripts/check-system-errors.php +214 -0
- package/scripts/compile.py +101 -0
- package/scripts/compiler/README_CONFIG.md +196 -0
- package/scripts/compiler/__init__.py +18 -0
- package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/binding_directive_service.py +103 -0
- package/scripts/compiler/class_binding_handler.py +347 -0
- package/scripts/compiler/cli.py +34 -0
- package/scripts/compiler/code_generator.py +141 -0
- package/scripts/compiler/compiler.config.json +36 -0
- package/scripts/compiler/compiler_utils.py +55 -0
- package/scripts/compiler/conditional_handlers.py +252 -0
- package/scripts/compiler/config.py +107 -0
- package/scripts/compiler/declaration_tracker.py +420 -0
- package/scripts/compiler/directive_processors.py +603 -0
- package/scripts/compiler/echo_processor.py +667 -0
- package/scripts/compiler/event_directive_processor.py +1099 -0
- package/scripts/compiler/fetch_parser.py +49 -0
- package/scripts/compiler/function_generators.py +310 -0
- package/scripts/compiler/loop_handlers.py +224 -0
- package/scripts/compiler/main_compiler.py +1763 -0
- package/scripts/compiler/parsers.py +1418 -0
- package/scripts/compiler/php_converter.py +470 -0
- package/scripts/compiler/php_js_converter.py +603 -0
- package/scripts/compiler/register_parser.py +480 -0
- package/scripts/compiler/section_handlers.py +122 -0
- package/scripts/compiler/show_directive_handler.py +85 -0
- package/scripts/compiler/style_directive_handler.py +169 -0
- package/scripts/compiler/template_analyzer.py +162 -0
- package/scripts/compiler/template_processor.py +1167 -0
- package/scripts/compiler/template_processors.py +1557 -0
- package/scripts/compiler/test_compiler.py +69 -0
- package/scripts/compiler/utils.py +54 -0
- package/scripts/compiler/variables_analyzer.py +135 -0
- package/scripts/compiler/view_identifier_generator.py +278 -0
- package/scripts/compiler/wrapper_parser.py +78 -0
- package/scripts/dev-context.js +311 -0
- package/scripts/dev.js +109 -0
- package/scripts/generate-assets-order.js +200 -0
- package/scripts/migrate-namespace.php +146 -0
- package/scripts/node/MIGRATION.md +190 -0
- package/scripts/node/README.md +269 -0
- package/scripts/node/build.js +208 -0
- package/scripts/node/compiler/compiler-utils.js +38 -0
- package/scripts/node/compiler/conditional-handlers.js +45 -0
- package/scripts/node/compiler/config.js +178 -0
- package/scripts/node/compiler/directive-processors.js +51 -0
- package/scripts/node/compiler/event-directive-processor.js +182 -0
- package/scripts/node/compiler/function-generators.js +239 -0
- package/scripts/node/compiler/loop-handlers.js +45 -0
- package/scripts/node/compiler/main-compiler.js +236 -0
- package/scripts/node/compiler/parsers.js +358 -0
- package/scripts/node/compiler/php-converter.js +227 -0
- package/scripts/node/compiler/register-parser.js +32 -0
- package/scripts/node/compiler/section-handlers.js +46 -0
- package/scripts/node/compiler/template-analyzer.js +50 -0
- package/scripts/node/compiler/template-processor.js +371 -0
- package/scripts/node/compiler/template-processors.js +219 -0
- package/scripts/node/compiler/utils.js +203 -0
- package/scripts/node/compiler/wrapper-parser.js +25 -0
- package/scripts/node/package.json +24 -0
- package/scripts/node/test-compiler.js +52 -0
- package/scripts/node-run.cjs +28 -0
- package/scripts/standardize-directories.php +92 -0
- package/templates/view.module.js +2 -0
- package/templates/view.tpl-raw.js +13 -0
- package/templates/wraper.js +71 -0
|
@@ -0,0 +1,1099 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event Directive Processor - Xử lý event directives theo format mới
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
class EventDirectiveProcessor:
|
|
8
|
+
def __init__(self, usestate_variables=None):
|
|
9
|
+
"""
|
|
10
|
+
Initialize Event Directive Processor
|
|
11
|
+
@param usestate_variables: Set of variable names that have useState declarations
|
|
12
|
+
"""
|
|
13
|
+
self.usestate_variables = usestate_variables or set()
|
|
14
|
+
|
|
15
|
+
def process_event_directive(self, event_type, expression):
|
|
16
|
+
"""
|
|
17
|
+
Process event directive theo format mới
|
|
18
|
+
Input: @click(handleClick(Event, 'test'))
|
|
19
|
+
Output: ${self.addEventConfig("click", [{"handler": "handleClick", "params": ["@EVENT", "test"]}])}
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
- Tất cả đều dùng __addEventConfig
|
|
23
|
+
- Hàm không có $ prefix: dùng object format {"handler": "name", "params": [...]}
|
|
24
|
+
- Biểu thức: dùng arrow function format (event) => ... hoặc () => ...
|
|
25
|
+
- Hàm có $ prefix:
|
|
26
|
+
- Nếu có useState → dùng (event) => setStateKey(...)
|
|
27
|
+
- Nếu không → dùng arrow function thông thường
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
raw_expr = expression.strip()
|
|
31
|
+
|
|
32
|
+
# Split by comma để xử lý từng phần
|
|
33
|
+
parts = self.split_by_comma(raw_expr)
|
|
34
|
+
|
|
35
|
+
# Phân loại các parts: handlers (không có $) vs expressions (có $ hoặc biểu thức)
|
|
36
|
+
# Tất cả đều dùng __addEventConfig, chỉ khác format:
|
|
37
|
+
# - Handler functions → object format: {"handler": "name", "params": [...]}
|
|
38
|
+
# - Expressions → arrow function format: (event) => ... hoặc () => ...
|
|
39
|
+
# Giữ nguyên thứ tự như trong input
|
|
40
|
+
handler_items = []
|
|
41
|
+
|
|
42
|
+
for part in parts:
|
|
43
|
+
part = part.strip()
|
|
44
|
+
if not part:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
# Kiểm tra xem có phải là function call không có $ prefix không
|
|
48
|
+
if self._is_function_call_without_dollar(part):
|
|
49
|
+
# Hàm không có $ prefix → dùng object format
|
|
50
|
+
handler = self.parse_handler(part)
|
|
51
|
+
if handler:
|
|
52
|
+
# Parameters đã được xử lý bởi parse_handler_parameters
|
|
53
|
+
# (có thể là object config string hoặc giá trị thông thường)
|
|
54
|
+
processed_params = handler['params']
|
|
55
|
+
|
|
56
|
+
# Build handler object
|
|
57
|
+
params_str = ','.join(processed_params)
|
|
58
|
+
handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str}]}}'
|
|
59
|
+
handler_items.append(handler_str)
|
|
60
|
+
else:
|
|
61
|
+
# Biểu thức hoặc hàm có $ prefix
|
|
62
|
+
# Kiểm tra xem có nested function calls phức tạp không
|
|
63
|
+
# Nếu có → dùng object handler để chắc chắn hơn
|
|
64
|
+
if self._has_nested_function_calls(part):
|
|
65
|
+
# Có nested calls → parse như handler function
|
|
66
|
+
# Xử lý cả trường hợp có $ prefix (state setter)
|
|
67
|
+
handler = self._parse_handler_with_dollar(part)
|
|
68
|
+
if handler:
|
|
69
|
+
# Parse và process parameters (có thể chứa function calls)
|
|
70
|
+
params_string = ', '.join(handler['params'])
|
|
71
|
+
processed_params = self.parse_handler_parameters(params_string)
|
|
72
|
+
|
|
73
|
+
# Build handler object
|
|
74
|
+
params_str = ','.join(processed_params)
|
|
75
|
+
handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str}]}}'
|
|
76
|
+
handler_items.append(handler_str)
|
|
77
|
+
else:
|
|
78
|
+
# Nếu không parse được → dùng arrow function
|
|
79
|
+
expressions = self._split_expressions_by_semicolon(part)
|
|
80
|
+
for expr in expressions:
|
|
81
|
+
expr_js = self._process_expression_to_arrow(expr)
|
|
82
|
+
handler_items.append(expr_js)
|
|
83
|
+
else:
|
|
84
|
+
# Không có nested calls → dùng arrow function format
|
|
85
|
+
# Tách biểu thức có `;` thành nhiều arrow functions
|
|
86
|
+
# Ví dụ: $a++; $b-- → [() => a++, () => b--]
|
|
87
|
+
expressions = self._split_expressions_by_semicolon(part)
|
|
88
|
+
for expr in expressions:
|
|
89
|
+
expr_js = self._process_expression_to_arrow(expr)
|
|
90
|
+
handler_items.append(expr_js)
|
|
91
|
+
|
|
92
|
+
# Luôn dùng __addEventConfig với mixed format, giữ nguyên thứ tự
|
|
93
|
+
if handler_items:
|
|
94
|
+
handlers_str = f'[{",".join(handler_items)}]'
|
|
95
|
+
event_config = f'this.__addEventConfig("{event_type}", {handlers_str})'
|
|
96
|
+
return f"${{{event_config}}}"
|
|
97
|
+
|
|
98
|
+
return ''
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"Event directive error: {e}")
|
|
102
|
+
return ''
|
|
103
|
+
|
|
104
|
+
def parse_event_handlers(self, expression):
|
|
105
|
+
"""
|
|
106
|
+
Parse multiple event handlers từ expression
|
|
107
|
+
"""
|
|
108
|
+
handlers = []
|
|
109
|
+
|
|
110
|
+
# Split by comma, respecting nested parentheses
|
|
111
|
+
handler_strings = self.split_by_comma(expression)
|
|
112
|
+
|
|
113
|
+
for handler_string in handler_strings:
|
|
114
|
+
handler_string = handler_string.strip()
|
|
115
|
+
if not handler_string:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
# Parse handler name and parameters
|
|
119
|
+
handler = self.parse_handler(handler_string)
|
|
120
|
+
if handler:
|
|
121
|
+
handlers.append(handler)
|
|
122
|
+
|
|
123
|
+
return handlers
|
|
124
|
+
|
|
125
|
+
def parse_handler(self, handler_string):
|
|
126
|
+
"""
|
|
127
|
+
Parse individual handler: handleClick(Event, 'test') or handleClick
|
|
128
|
+
Chỉ parse hàm không có $ prefix
|
|
129
|
+
"""
|
|
130
|
+
# Match function name and parameters
|
|
131
|
+
match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', handler_string, re.DOTALL)
|
|
132
|
+
if match:
|
|
133
|
+
handler_name = match.group(1)
|
|
134
|
+
params_string = match.group(2).strip()
|
|
135
|
+
|
|
136
|
+
# Parse parameters
|
|
137
|
+
params = self.parse_handler_parameters(params_string)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
'handler': handler_name,
|
|
141
|
+
'params': params
|
|
142
|
+
}
|
|
143
|
+
else:
|
|
144
|
+
# No parentheses - just function name
|
|
145
|
+
match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)$', handler_string.strip())
|
|
146
|
+
if match:
|
|
147
|
+
handler_name = match.group(1)
|
|
148
|
+
return {
|
|
149
|
+
'handler': handler_name,
|
|
150
|
+
'params': []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
def parse_handler_parameters(self, params_string):
|
|
156
|
+
"""
|
|
157
|
+
Parse handler parameters: Event, 'test', 123
|
|
158
|
+
Nếu param là function call → dùng object config
|
|
159
|
+
"""
|
|
160
|
+
if not params_string.strip():
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
# Split by comma, respecting nested structures
|
|
164
|
+
params = self.split_by_comma(params_string)
|
|
165
|
+
|
|
166
|
+
# Process each parameter
|
|
167
|
+
processed_params = []
|
|
168
|
+
for param in params:
|
|
169
|
+
param = param.strip()
|
|
170
|
+
|
|
171
|
+
# Kiểm tra xem có phải là function call không (có hoặc không có $ prefix)
|
|
172
|
+
# Nếu là function call → parse thành object config
|
|
173
|
+
if self._is_function_call_in_param(param):
|
|
174
|
+
handler = self._parse_function_call_in_param(param)
|
|
175
|
+
if handler:
|
|
176
|
+
# Build object config string
|
|
177
|
+
# in_params_context=True vì đang xử lý params trong object config
|
|
178
|
+
processed_handler_params = []
|
|
179
|
+
for p in handler['params']:
|
|
180
|
+
processed_handler_params.append(self.process_parameter(p.strip(), in_params_context=True))
|
|
181
|
+
params_str = ','.join(processed_handler_params)
|
|
182
|
+
handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str}]}}'
|
|
183
|
+
processed_params.append(handler_str)
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# Không phải function call → xử lý như bình thường
|
|
187
|
+
# in_params_context=True vì đang xử lý params trong object config
|
|
188
|
+
processed_param = self.process_parameter(param, in_params_context=True)
|
|
189
|
+
processed_params.append(processed_param)
|
|
190
|
+
|
|
191
|
+
return processed_params
|
|
192
|
+
|
|
193
|
+
def _is_function_call_in_param(self, param):
|
|
194
|
+
"""
|
|
195
|
+
Kiểm tra xem param có phải là function call không (có hoặc không có $ prefix)
|
|
196
|
+
Ví dụ: outerFunc($count, @event) → True
|
|
197
|
+
Ví dụ: $setCount($count + 1) → True
|
|
198
|
+
Ví dụ: $count → False
|
|
199
|
+
"""
|
|
200
|
+
param = param.strip()
|
|
201
|
+
# Match function call pattern: name(...) hoặc $name(...)
|
|
202
|
+
match = re.match(r'^(\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\(', param, re.DOTALL)
|
|
203
|
+
return match is not None
|
|
204
|
+
|
|
205
|
+
def _parse_function_call_in_param(self, param):
|
|
206
|
+
"""
|
|
207
|
+
Parse function call trong param thành handler object
|
|
208
|
+
Ví dụ: outerFunc($count, @event) → {"handler": "outerFunc", "params": ["$count", "@event"]}
|
|
209
|
+
Ví dụ: $setCount($count + 1) → {"handler": "setCount", "params": ["$count + 1"]}
|
|
210
|
+
"""
|
|
211
|
+
param = param.strip()
|
|
212
|
+
|
|
213
|
+
# Match function call với hoặc không có $ prefix
|
|
214
|
+
match = re.match(r'^(\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', param, re.DOTALL)
|
|
215
|
+
if match:
|
|
216
|
+
func_name_with_dollar = match.group(1)
|
|
217
|
+
params_string = match.group(2).strip()
|
|
218
|
+
|
|
219
|
+
# Xử lý tên hàm
|
|
220
|
+
if func_name_with_dollar.startswith('$'):
|
|
221
|
+
func_name = func_name_with_dollar[1:] # Bỏ $ prefix
|
|
222
|
+
# Kiểm tra xem có phải là state variable không
|
|
223
|
+
if func_name in self.usestate_variables:
|
|
224
|
+
# Là state setter → dùng tên setter
|
|
225
|
+
handler_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
|
|
226
|
+
else:
|
|
227
|
+
# Không phải state variable → dùng tên hàm bình thường
|
|
228
|
+
handler_name = func_name
|
|
229
|
+
else:
|
|
230
|
+
# Không có $ prefix
|
|
231
|
+
handler_name = func_name_with_dollar
|
|
232
|
+
|
|
233
|
+
# Parse parameters
|
|
234
|
+
params = self.split_by_comma(params_string)
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
'handler': handler_name,
|
|
238
|
+
'params': params
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def _parse_handler_with_dollar(self, handler_string):
|
|
244
|
+
"""
|
|
245
|
+
Parse handler có $ prefix (state setter)
|
|
246
|
+
Ví dụ: $setCount(nestedCall($count, $count + 1))
|
|
247
|
+
→ {"handler": "setCount", "params": ["nestedCall($count, $count + 1)"]}
|
|
248
|
+
"""
|
|
249
|
+
# Match function call với $ prefix: $name(...)
|
|
250
|
+
match = re.match(r'^\$([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', handler_string, re.DOTALL)
|
|
251
|
+
if match:
|
|
252
|
+
func_name = match.group(1)
|
|
253
|
+
params_string = match.group(2).strip()
|
|
254
|
+
|
|
255
|
+
# Kiểm tra xem có phải là state variable không
|
|
256
|
+
if func_name in self.usestate_variables:
|
|
257
|
+
# Là state setter → dùng tên setter
|
|
258
|
+
# If func_name already starts with "set", use it as is (it's already a setter name)
|
|
259
|
+
# Otherwise, capitalize first letter: count -> setCount
|
|
260
|
+
if func_name.startswith('set') and len(func_name) > 3:
|
|
261
|
+
setter_name = func_name # Already a setter name like "setCount"
|
|
262
|
+
else:
|
|
263
|
+
setter_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
|
|
264
|
+
|
|
265
|
+
# Parse parameters
|
|
266
|
+
params = self.split_by_comma(params_string)
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
'handler': setter_name,
|
|
270
|
+
'params': params
|
|
271
|
+
}
|
|
272
|
+
else:
|
|
273
|
+
# Không phải state variable → dùng tên hàm bình thường (bỏ $)
|
|
274
|
+
params = self.split_by_comma(params_string)
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
'handler': func_name,
|
|
278
|
+
'params': params
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
def _has_event_with_access(self, param):
|
|
284
|
+
"""
|
|
285
|
+
Kiểm tra xem param có chứa $event với property access hoặc method call không
|
|
286
|
+
Ví dụ: $event.target, $event.preventDefault(), test($event.type)
|
|
287
|
+
"""
|
|
288
|
+
# Pattern: $event theo sau bởi . hoặc chữ khác (property/method access)
|
|
289
|
+
# Hoặc $event nằm trong function call: func($event.prop)
|
|
290
|
+
pattern = r'\$(?:event|Event|EVENT)\s*\.'
|
|
291
|
+
return re.search(pattern, param, re.IGNORECASE) is not None
|
|
292
|
+
|
|
293
|
+
def _convert_event_with_access_to_arrow(self, param):
|
|
294
|
+
"""
|
|
295
|
+
Convert expression có $event.property hoặc $event.method() thành arrow function
|
|
296
|
+
Input: $event.target → Output: (event) => event.target
|
|
297
|
+
Input: test($event.type, $message) → Output: (event) => test(event.type, message)
|
|
298
|
+
"""
|
|
299
|
+
# Convert $event/$Event/$EVENT thành event (JavaScript variable)
|
|
300
|
+
result = re.sub(r'\$(?:event|Event|EVENT)(?![a-zA-Z])', 'event', param, flags=re.IGNORECASE)
|
|
301
|
+
|
|
302
|
+
# Convert các PHP variables khác thành JS variables
|
|
303
|
+
result = self.convert_php_variable_to_js(result)
|
|
304
|
+
|
|
305
|
+
# Convert PHP array syntax to JS object
|
|
306
|
+
result = self.convert_php_array_to_js_object(result)
|
|
307
|
+
|
|
308
|
+
# Wrap trong arrow function
|
|
309
|
+
return f'(event) => {result}'
|
|
310
|
+
|
|
311
|
+
def process_parameter(self, param, in_params_context=False):
|
|
312
|
+
"""
|
|
313
|
+
Process individual parameter để handle special cases
|
|
314
|
+
Nếu là biểu thức → trả về (event) => ... format
|
|
315
|
+
Nếu không → trả về giá trị thông thường
|
|
316
|
+
in_params_context: True nếu đang xử lý params trong object config
|
|
317
|
+
"""
|
|
318
|
+
# Nếu đã là object config string (có dạng {"handler":...}), không xử lý lại
|
|
319
|
+
if isinstance(param, str) and param.strip().startswith('{"handler"'):
|
|
320
|
+
return param
|
|
321
|
+
|
|
322
|
+
# SPECIAL CASE: Nếu có $event với property/method access → convert thành arrow function
|
|
323
|
+
# Ví dụ: $event.target → (event) => event.target
|
|
324
|
+
# Phải làm TRƯỚC các bước normalize khác
|
|
325
|
+
if self._has_event_with_access(param):
|
|
326
|
+
return self._convert_event_with_access_to_arrow(param)
|
|
327
|
+
|
|
328
|
+
# QUAN TRỌNG: Normalize event aliases TRƯỚC khi convert PHP variables
|
|
329
|
+
# Thêm alias support: $event/$Event/$EVENT → @EVENT (phải làm trước convert_php_variable_to_js)
|
|
330
|
+
param = re.sub(r'\$(?:event|Event|EVENT)(?![a-zA-Z])', '@EVENT', param, flags=re.IGNORECASE)
|
|
331
|
+
|
|
332
|
+
# Đảm bảo @event/@Event/@EVENT được chuyển thành @EVENT (chuẩn hóa)
|
|
333
|
+
param = re.sub(r'@(?:event|Event|EVENT)(?![a-zA-Z])', '@EVENT', param, flags=re.IGNORECASE)
|
|
334
|
+
|
|
335
|
+
# @EVENT luôn là chuỗi "@EVENT" để hệ thống thay thế sau
|
|
336
|
+
if param.strip() == '@EVENT':
|
|
337
|
+
return '"@EVENT"'
|
|
338
|
+
|
|
339
|
+
# Convert PHP variable to JavaScript variable ($item -> item)
|
|
340
|
+
# Phải làm SAU khi đã normalize event aliases
|
|
341
|
+
param = self.convert_php_variable_to_js(param)
|
|
342
|
+
|
|
343
|
+
# Xử lý Event parameters trong mọi context
|
|
344
|
+
param = self.process_event_in_string(param)
|
|
345
|
+
|
|
346
|
+
# Xử lý @attr(...) -> "#ATTR:..."
|
|
347
|
+
param = self.process_attr_prop_in_string(param, '@attr', '#ATTR')
|
|
348
|
+
|
|
349
|
+
# Xử lý @prop(...) -> "#PROP:..."
|
|
350
|
+
param = self.process_attr_prop_in_string(param, '@prop', '#PROP')
|
|
351
|
+
|
|
352
|
+
# Xử lý @val(...) -> "#VALUE:..."
|
|
353
|
+
param = self.process_attr_prop_in_string(param, '@val', '#VALUE')
|
|
354
|
+
|
|
355
|
+
# Xử lý @value(...) -> "#VALUE:..."
|
|
356
|
+
param = self.process_attr_prop_in_string(param, '@value', '#VALUE')
|
|
357
|
+
|
|
358
|
+
# Convert PHP array syntax to JavaScript object syntax
|
|
359
|
+
param = self.convert_php_array_to_js_object(param)
|
|
360
|
+
|
|
361
|
+
# Nếu đã là arrow function (từ các bước xử lý trước), không wrap lại
|
|
362
|
+
if '=>' in param:
|
|
363
|
+
# Nhưng vẫn cần thay thế @EVENT trong arrow function nếu có
|
|
364
|
+
param = re.sub(r'(?<!")@(?:EVENT|event|Event)(?!")', '"@EVENT"', param)
|
|
365
|
+
return param
|
|
366
|
+
|
|
367
|
+
# Thay thế @EVENT/@event/@Event (không có quotes) thành "@EVENT" trong expression
|
|
368
|
+
# Sử dụng regex để tránh thay thế @EVENT đã có quotes
|
|
369
|
+
param = re.sub(r'(?<!")@(?:EVENT|event|Event)(?!")', '"@EVENT"', param, flags=re.IGNORECASE)
|
|
370
|
+
|
|
371
|
+
# Nếu đang xử lý params trong object config và param là biến đơn giản
|
|
372
|
+
# (không phải string, không phải directive đã xử lý) → wrap trong () => để lấy giá trị runtime
|
|
373
|
+
if in_params_context:
|
|
374
|
+
# Kiểm tra xem có phải là string (đã có quotes) không
|
|
375
|
+
is_string = (param.startswith('"') and param.endswith('"')) or (param.startswith("'") and param.endswith("'"))
|
|
376
|
+
# Kiểm tra xem có phải là directive đã xử lý không (#ATTR, #PROP, #VALUE)
|
|
377
|
+
is_directive = param.startswith('"#') or param.startswith("'#")
|
|
378
|
+
# Kiểm tra xem có phải là biến đơn giản (valid JS identifier) không
|
|
379
|
+
is_simple_var = self._is_valid_js_identifier(param) and not is_string and not is_directive
|
|
380
|
+
|
|
381
|
+
if is_simple_var:
|
|
382
|
+
return f'() => {param}'
|
|
383
|
+
|
|
384
|
+
# Nếu là biểu thức → wrap trong arrow function
|
|
385
|
+
if self._looks_like_expression(param):
|
|
386
|
+
return f'(event) => {param}'
|
|
387
|
+
|
|
388
|
+
return param
|
|
389
|
+
|
|
390
|
+
def process_data_type(self, param):
|
|
391
|
+
"""
|
|
392
|
+
Process parameter để xác định đúng kiểu dữ liệu
|
|
393
|
+
"""
|
|
394
|
+
param = param.strip()
|
|
395
|
+
|
|
396
|
+
# Nếu đã có quotes thì giữ nguyên
|
|
397
|
+
if (param.startswith('"') and param.endswith('"')) or (param.startswith("'") and param.endswith("'")):
|
|
398
|
+
return param
|
|
399
|
+
|
|
400
|
+
# Nếu là object hoặc array JavaScript thì giữ nguyên
|
|
401
|
+
if (param.startswith('{') and param.endswith('}')) or (param.startswith('[') and param.endswith(']')):
|
|
402
|
+
return param
|
|
403
|
+
|
|
404
|
+
# Nếu là number thì giữ nguyên
|
|
405
|
+
if param.isdigit() or (param.startswith('-') and param[1:].isdigit()):
|
|
406
|
+
return param
|
|
407
|
+
|
|
408
|
+
# Nếu là boolean thì giữ nguyên
|
|
409
|
+
if param.lower() in ['true', 'false']:
|
|
410
|
+
return param
|
|
411
|
+
|
|
412
|
+
# Nếu là null thì giữ nguyên
|
|
413
|
+
if param.lower() == 'null':
|
|
414
|
+
return param
|
|
415
|
+
|
|
416
|
+
# Nếu là string thì wrap trong quotes
|
|
417
|
+
return f'"{param}"'
|
|
418
|
+
|
|
419
|
+
def convert_php_array_to_js_object(self, param):
|
|
420
|
+
"""
|
|
421
|
+
Convert PHP array syntax to JavaScript object syntax
|
|
422
|
+
['key' => 'value'] -> {"key": "value"}
|
|
423
|
+
"""
|
|
424
|
+
# Use the new PHP to JS converter with proper precedence
|
|
425
|
+
from php_converter import convert_php_to_js
|
|
426
|
+
param = convert_php_to_js(param)
|
|
427
|
+
|
|
428
|
+
# If it's already a JavaScript object/array, return as is
|
|
429
|
+
if param.startswith('{') and param.endswith('}'):
|
|
430
|
+
return param
|
|
431
|
+
if param.startswith('[') and param.endswith(']') and ' => ' not in param:
|
|
432
|
+
return param
|
|
433
|
+
|
|
434
|
+
# If it's a PHP array, convert to JavaScript object
|
|
435
|
+
if param.startswith('[') and param.endswith(']'):
|
|
436
|
+
# Use advanced converter for complex structures
|
|
437
|
+
from php_js_converter import php_to_js_advanced
|
|
438
|
+
param = php_to_js_advanced(param)
|
|
439
|
+
|
|
440
|
+
return param
|
|
441
|
+
|
|
442
|
+
def _fix_nested_arrays(self, param):
|
|
443
|
+
"""
|
|
444
|
+
Fix nested arrays in converted object
|
|
445
|
+
{"test": {"hahaha", 12}} -> {"test": ["hahaha", 12]}
|
|
446
|
+
"""
|
|
447
|
+
# Find patterns like {"value1", "value2"} and convert to ["value1", "value2"]
|
|
448
|
+
import re
|
|
449
|
+
|
|
450
|
+
# Pattern to match {"value1", "value2"} - array values
|
|
451
|
+
pattern = r'\{\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\}'
|
|
452
|
+
|
|
453
|
+
def replace_array(match):
|
|
454
|
+
val1 = match.group(1)
|
|
455
|
+
val2 = match.group(2)
|
|
456
|
+
return f'["{val1}", "{val2}"]'
|
|
457
|
+
|
|
458
|
+
param = re.sub(pattern, replace_array, param)
|
|
459
|
+
|
|
460
|
+
# Pattern to match {"value1", 123} - mixed array values
|
|
461
|
+
pattern2 = r'\{\s*"([^"]+)"\s*,\s*(\d+)\s*\}'
|
|
462
|
+
|
|
463
|
+
def replace_mixed_array(match):
|
|
464
|
+
val1 = match.group(1)
|
|
465
|
+
val2 = match.group(2)
|
|
466
|
+
return f'["{val1}", {val2}]'
|
|
467
|
+
|
|
468
|
+
param = re.sub(pattern2, replace_mixed_array, param)
|
|
469
|
+
|
|
470
|
+
return param
|
|
471
|
+
|
|
472
|
+
def process_event_in_string(self, param):
|
|
473
|
+
"""
|
|
474
|
+
Process Event parameters trong string một cách recursive
|
|
475
|
+
Không xử lý 'event' trong arrow function như (event) => ...
|
|
476
|
+
"""
|
|
477
|
+
# Nếu đã là arrow function, không xử lý (tránh match 'event' trong (event) =>)
|
|
478
|
+
if '=>' in param:
|
|
479
|
+
return param
|
|
480
|
+
|
|
481
|
+
# Nếu có pattern (event) hoặc (Event), không xử lý (đó là arrow function parameter)
|
|
482
|
+
if re.search(r'\(\s*event\s*\)', param, re.IGNORECASE):
|
|
483
|
+
return param
|
|
484
|
+
|
|
485
|
+
# Tìm tất cả Event variations trong string
|
|
486
|
+
# Match @event, @Event, @EVENT (với @ prefix)
|
|
487
|
+
# Không match 'event' đơn lẻ (tránh match trong các từ khác)
|
|
488
|
+
# Cho phép @event trước dấu ), dấu phẩy, khoảng trắng, hoặc kết thúc string
|
|
489
|
+
# Pattern: @event, @Event, @EVENT (không có chữ cái sau)
|
|
490
|
+
event_pattern = r'@(?:Event|EVENT|event)(?![a-zA-Z])'
|
|
491
|
+
param = re.sub(event_pattern, '@EVENT', param, flags=re.IGNORECASE)
|
|
492
|
+
|
|
493
|
+
# Thêm alias support: $event, $Event, $EVENT cũng chuyển thành @EVENT
|
|
494
|
+
# Pattern: $event, $Event, $EVENT (không có chữ cái sau)
|
|
495
|
+
dollar_event_pattern = r'\$(?:Event|EVENT|event)(?![a-zA-Z])'
|
|
496
|
+
param = re.sub(dollar_event_pattern, '@EVENT', param, flags=re.IGNORECASE)
|
|
497
|
+
|
|
498
|
+
return param
|
|
499
|
+
|
|
500
|
+
def process_attr_prop_in_string(self, param, directive, prefix):
|
|
501
|
+
"""
|
|
502
|
+
Process @attr, @prop, @val, @value trong string một cách recursive
|
|
503
|
+
"""
|
|
504
|
+
# Tìm tất cả @directive(...) hoặc @directive() trong string
|
|
505
|
+
pattern = re.escape(directive) + r'\s*\(\s*([^)]*)\s*\)'
|
|
506
|
+
|
|
507
|
+
def replace_match(match):
|
|
508
|
+
value = match.group(1).strip()
|
|
509
|
+
|
|
510
|
+
if not value:
|
|
511
|
+
# Trường hợp @directive() - không có tham số
|
|
512
|
+
return f'"{prefix}"'
|
|
513
|
+
else:
|
|
514
|
+
# Loại bỏ quotes nếu có
|
|
515
|
+
value = value.strip('"\'')
|
|
516
|
+
# Thay thế @directive(...) bằng "#PREFIX:..." hoặc "#PREFIX"
|
|
517
|
+
return f'"{prefix}:{value}"'
|
|
518
|
+
|
|
519
|
+
param = re.sub(pattern, replace_match, param, flags=re.IGNORECASE)
|
|
520
|
+
|
|
521
|
+
return param
|
|
522
|
+
|
|
523
|
+
def _split_expressions_by_semicolon(self, expr):
|
|
524
|
+
"""
|
|
525
|
+
Tách biểu thức có `;` thành nhiều biểu thức
|
|
526
|
+
Ví dụ: $a++; $b-- → ['$a++', '$b--']
|
|
527
|
+
"""
|
|
528
|
+
if ';' not in expr:
|
|
529
|
+
return [expr]
|
|
530
|
+
|
|
531
|
+
expressions = []
|
|
532
|
+
current = ''
|
|
533
|
+
paren_count = 0
|
|
534
|
+
bracket_count = 0
|
|
535
|
+
in_quotes = False
|
|
536
|
+
quote_char = ''
|
|
537
|
+
|
|
538
|
+
i = 0
|
|
539
|
+
while i < len(expr):
|
|
540
|
+
char = expr[i]
|
|
541
|
+
|
|
542
|
+
if not in_quotes:
|
|
543
|
+
if char in ['"', "'"]:
|
|
544
|
+
in_quotes = True
|
|
545
|
+
quote_char = char
|
|
546
|
+
current += char
|
|
547
|
+
elif char == '(':
|
|
548
|
+
paren_count += 1
|
|
549
|
+
current += char
|
|
550
|
+
elif char == ')':
|
|
551
|
+
paren_count -= 1
|
|
552
|
+
current += char
|
|
553
|
+
elif char == '[':
|
|
554
|
+
bracket_count += 1
|
|
555
|
+
current += char
|
|
556
|
+
elif char == ']':
|
|
557
|
+
bracket_count -= 1
|
|
558
|
+
current += char
|
|
559
|
+
elif char == ';' and paren_count == 0 and bracket_count == 0:
|
|
560
|
+
# Semicolon at level 0 - expression separator
|
|
561
|
+
if current.strip():
|
|
562
|
+
expressions.append(current.strip())
|
|
563
|
+
current = ''
|
|
564
|
+
i += 1
|
|
565
|
+
continue
|
|
566
|
+
else:
|
|
567
|
+
current += char
|
|
568
|
+
else:
|
|
569
|
+
current += char
|
|
570
|
+
if char == quote_char and (i == 0 or expr[i-1] != '\\'):
|
|
571
|
+
in_quotes = False
|
|
572
|
+
quote_char = ''
|
|
573
|
+
|
|
574
|
+
i += 1
|
|
575
|
+
|
|
576
|
+
# Add last expression
|
|
577
|
+
if current.strip():
|
|
578
|
+
expressions.append(current.strip())
|
|
579
|
+
|
|
580
|
+
return expressions if expressions else [expr]
|
|
581
|
+
|
|
582
|
+
def split_by_comma(self, expression):
|
|
583
|
+
"""
|
|
584
|
+
Split expression by comma, respecting nested parentheses, square brackets and quotes
|
|
585
|
+
"""
|
|
586
|
+
params = []
|
|
587
|
+
current = ''
|
|
588
|
+
paren_count = 0
|
|
589
|
+
bracket_count = 0
|
|
590
|
+
in_quotes = False
|
|
591
|
+
quote_char = ''
|
|
592
|
+
|
|
593
|
+
i = 0
|
|
594
|
+
while i < len(expression):
|
|
595
|
+
char = expression[i]
|
|
596
|
+
|
|
597
|
+
if not in_quotes:
|
|
598
|
+
if char in ['"', "'"]:
|
|
599
|
+
in_quotes = True
|
|
600
|
+
quote_char = char
|
|
601
|
+
current += char
|
|
602
|
+
elif char == '(':
|
|
603
|
+
paren_count += 1
|
|
604
|
+
current += char
|
|
605
|
+
elif char == ')':
|
|
606
|
+
paren_count -= 1
|
|
607
|
+
current += char
|
|
608
|
+
elif char == '[':
|
|
609
|
+
bracket_count += 1
|
|
610
|
+
current += char
|
|
611
|
+
elif char == ']':
|
|
612
|
+
bracket_count -= 1
|
|
613
|
+
current += char
|
|
614
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
615
|
+
# Comma at level 0 - parameter separator
|
|
616
|
+
params.append(current.strip())
|
|
617
|
+
current = ''
|
|
618
|
+
i += 1
|
|
619
|
+
continue
|
|
620
|
+
else:
|
|
621
|
+
current += char
|
|
622
|
+
else:
|
|
623
|
+
current += char
|
|
624
|
+
if char == quote_char and (i == 0 or expression[i-1] != '\\'):
|
|
625
|
+
in_quotes = False
|
|
626
|
+
quote_char = ''
|
|
627
|
+
|
|
628
|
+
i += 1
|
|
629
|
+
|
|
630
|
+
# Add last parameter
|
|
631
|
+
if current.strip():
|
|
632
|
+
params.append(current.strip())
|
|
633
|
+
|
|
634
|
+
return params
|
|
635
|
+
|
|
636
|
+
def build_event_config(self, event_type, handlers):
|
|
637
|
+
"""
|
|
638
|
+
Build event config theo format mới
|
|
639
|
+
"""
|
|
640
|
+
handler_configs = []
|
|
641
|
+
|
|
642
|
+
for handler in handlers:
|
|
643
|
+
handler_name = handler['handler']
|
|
644
|
+
params = handler['params']
|
|
645
|
+
|
|
646
|
+
# Process parameters để handle special cases
|
|
647
|
+
processed_params = []
|
|
648
|
+
for param in params:
|
|
649
|
+
processed_params.append(self.process_parameter(param))
|
|
650
|
+
|
|
651
|
+
handler_configs.append({
|
|
652
|
+
'handler': handler_name,
|
|
653
|
+
'params': processed_params
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
# Build JavaScript array string manually để control quoting
|
|
657
|
+
handlers_str = self.build_handlers_string(handler_configs)
|
|
658
|
+
|
|
659
|
+
return f'this.__addEventConfig("{event_type}", {handlers_str})'
|
|
660
|
+
|
|
661
|
+
def build_handlers_string(self, handler_configs):
|
|
662
|
+
"""
|
|
663
|
+
Build JavaScript array string manually để control quoting
|
|
664
|
+
"""
|
|
665
|
+
handlers = []
|
|
666
|
+
|
|
667
|
+
for config in handler_configs:
|
|
668
|
+
handler_name = config['handler']
|
|
669
|
+
params = config['params']
|
|
670
|
+
|
|
671
|
+
# Build params array with proper quoting
|
|
672
|
+
processed_params = []
|
|
673
|
+
for param in params:
|
|
674
|
+
# Param đã được xử lý bởi process_parameter, có thể đã là arrow function
|
|
675
|
+
# Kiểm tra xem đã là arrow function chưa
|
|
676
|
+
if isinstance(param, str) and ('=>' in param or param.startswith('(event) =>') or param.startswith('() =>')):
|
|
677
|
+
# Đã là arrow function → giữ nguyên
|
|
678
|
+
processed_params.append(param)
|
|
679
|
+
continue
|
|
680
|
+
|
|
681
|
+
# @EVENT đã được xử lý trong process_parameter thành "@EVENT" (có quotes)
|
|
682
|
+
if isinstance(param, str) and (param == '@EVENT' or param == '"@EVENT"'):
|
|
683
|
+
processed_params.append('"@EVENT"')
|
|
684
|
+
continue
|
|
685
|
+
|
|
686
|
+
if isinstance(param, str):
|
|
687
|
+
# First, convert PHP variable to JS if needed ($item -> item)
|
|
688
|
+
param = self.convert_php_variable_to_js(param)
|
|
689
|
+
|
|
690
|
+
# If it's a string and not already quoted, check what it is
|
|
691
|
+
if not (param.startswith('"') and param.endswith('"')) and not (param.startswith("'") and param.endswith("'")):
|
|
692
|
+
# Check if it's a complex structure (array/object)
|
|
693
|
+
if param.startswith('[') and param.endswith(']'):
|
|
694
|
+
processed_params.append(param)
|
|
695
|
+
elif param.startswith('{') and param.endswith('}'):
|
|
696
|
+
processed_params.append(param)
|
|
697
|
+
elif param.startswith('@'):
|
|
698
|
+
# Special directive like @EVENT
|
|
699
|
+
processed_params.append(f'"{param}"')
|
|
700
|
+
elif self._is_valid_js_identifier(param):
|
|
701
|
+
# Valid JavaScript variable/identifier - don't wrap in quotes
|
|
702
|
+
processed_params.append(param)
|
|
703
|
+
else:
|
|
704
|
+
# Simple string value
|
|
705
|
+
processed_params.append(f'"{param}"')
|
|
706
|
+
else:
|
|
707
|
+
processed_params.append(param)
|
|
708
|
+
else:
|
|
709
|
+
# Convert to string for non-string types
|
|
710
|
+
processed_params.append(str(param))
|
|
711
|
+
|
|
712
|
+
params_str = ','.join(processed_params)
|
|
713
|
+
|
|
714
|
+
# Build handler object
|
|
715
|
+
handler_str = f'{{"handler":"{handler_name}","params":[{params_str}]}}'
|
|
716
|
+
handlers.append(handler_str)
|
|
717
|
+
|
|
718
|
+
return f'[{",".join(handlers)}]'
|
|
719
|
+
|
|
720
|
+
def _looks_like_expression(self, s: str) -> bool:
|
|
721
|
+
"""
|
|
722
|
+
Phát hiện chuỗi là biểu thức JS (không phải literal/identifier đơn)
|
|
723
|
+
- Chứa toán tử, dấu ngoặc, hoặc gọi hàm
|
|
724
|
+
- Không phải chỉ là số, boolean, null, hay identifier hợp lệ
|
|
725
|
+
- Không phải @EVENT (đã là special directive)
|
|
726
|
+
"""
|
|
727
|
+
t = s.strip()
|
|
728
|
+
|
|
729
|
+
# Nếu đã là arrow function, không phải biểu thức đơn giản
|
|
730
|
+
if '=>' in t:
|
|
731
|
+
return False
|
|
732
|
+
|
|
733
|
+
# @EVENT là special directive, không phải biểu thức
|
|
734
|
+
if t == '@EVENT' or t == '"@EVENT"':
|
|
735
|
+
return False
|
|
736
|
+
|
|
737
|
+
# literals
|
|
738
|
+
if t.isdigit() or (t.startswith('-') and t[1:].isdigit()):
|
|
739
|
+
return False
|
|
740
|
+
if t.lower() in ['true', 'false', 'null']:
|
|
741
|
+
return False
|
|
742
|
+
if self._is_valid_js_identifier(t):
|
|
743
|
+
return False
|
|
744
|
+
# function call or operators/parentheses
|
|
745
|
+
op_chars = ['+', '-', '*', '/', '%', '>', '<', '=', '!', '&', '|', '?', ':']
|
|
746
|
+
if '(' in t and ')' in t:
|
|
747
|
+
return True
|
|
748
|
+
if any(ch in t for ch in op_chars):
|
|
749
|
+
return True
|
|
750
|
+
# spaces between tokens can indicate expression
|
|
751
|
+
if ' ' in t:
|
|
752
|
+
return True
|
|
753
|
+
return False
|
|
754
|
+
|
|
755
|
+
def _looks_like_handlers(self, expr: str) -> bool:
|
|
756
|
+
"""
|
|
757
|
+
Kiểm tra biểu thức giống danh sách handler function calls, ví dụ: fn(a), fn2(), handle
|
|
758
|
+
"""
|
|
759
|
+
parts = self.split_by_comma(expr)
|
|
760
|
+
if not parts:
|
|
761
|
+
return False
|
|
762
|
+
for p in parts:
|
|
763
|
+
p = p.strip()
|
|
764
|
+
# Check if it's a function call with parameters
|
|
765
|
+
match = re.match(r'^[a-zA-Z_$][a-zA-Z0-9_$]*\s*\((.*)\)$', p)
|
|
766
|
+
if match:
|
|
767
|
+
params_str = match.group(1).strip()
|
|
768
|
+
# If parameters contain expressions, this should be quickHandle
|
|
769
|
+
if params_str and self._contains_expressions(params_str):
|
|
770
|
+
return False # Not a simple handler, use quickHandle
|
|
771
|
+
elif not re.match(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$', p):
|
|
772
|
+
# Not a simple variable or function call
|
|
773
|
+
return False
|
|
774
|
+
return True
|
|
775
|
+
|
|
776
|
+
def _contains_expressions(self, params_str: str) -> bool:
|
|
777
|
+
"""
|
|
778
|
+
Check if parameter string contains expressions (operators, method calls, etc.)
|
|
779
|
+
"""
|
|
780
|
+
params_str = params_str.strip()
|
|
781
|
+
if not params_str:
|
|
782
|
+
return False
|
|
783
|
+
|
|
784
|
+
# Check for mathematical operators
|
|
785
|
+
if re.search(r'[+\-*/%]', params_str):
|
|
786
|
+
return True
|
|
787
|
+
|
|
788
|
+
# Check for method calls like obj.method() or obj->method()
|
|
789
|
+
if '->' in params_str or re.search(r'\.[a-zA-Z_]', params_str):
|
|
790
|
+
return True
|
|
791
|
+
|
|
792
|
+
# Check for array access like arr[0]
|
|
793
|
+
if re.search(r'\[.*\]', params_str):
|
|
794
|
+
return True
|
|
795
|
+
|
|
796
|
+
# Check for ternary operators
|
|
797
|
+
if '?' in params_str and ':' in params_str:
|
|
798
|
+
return True
|
|
799
|
+
|
|
800
|
+
return False
|
|
801
|
+
|
|
802
|
+
def _is_valid_js_identifier(self, param):
|
|
803
|
+
"""
|
|
804
|
+
Check if param is a valid JavaScript identifier/variable name
|
|
805
|
+
Valid identifiers: letters, digits, underscore, dollar sign
|
|
806
|
+
Must start with letter, underscore, or dollar sign
|
|
807
|
+
Examples: item, userState, _private, $local
|
|
808
|
+
"""
|
|
809
|
+
# JavaScript identifier pattern
|
|
810
|
+
js_identifier_pattern = r'^[a-zA-Z_$][a-zA-Z0-9_$]*$'
|
|
811
|
+
return bool(re.match(js_identifier_pattern, param))
|
|
812
|
+
|
|
813
|
+
def convert_php_variable_to_js(self, param):
|
|
814
|
+
"""
|
|
815
|
+
Convert PHP variable syntax to JavaScript
|
|
816
|
+
$item -> item
|
|
817
|
+
"""
|
|
818
|
+
# Pattern to match PHP variables: $variableName
|
|
819
|
+
php_var_pattern = r'\$([a-zA-Z_][a-zA-Z0-9_]*)'
|
|
820
|
+
# Replace $variable with variable
|
|
821
|
+
param = re.sub(php_var_pattern, r'\1', param)
|
|
822
|
+
return param
|
|
823
|
+
|
|
824
|
+
def _has_nested_function_calls(self, expr):
|
|
825
|
+
"""
|
|
826
|
+
Kiểm tra xem expression có chứa nested function calls không
|
|
827
|
+
Ví dụ: nestedCall(outerFunc(...), innerFunc(...)) → True
|
|
828
|
+
Ví dụ: $setCount(nestedCall(...)) → True
|
|
829
|
+
Ví dụ: test($a, $b) → False
|
|
830
|
+
"""
|
|
831
|
+
expr = expr.strip()
|
|
832
|
+
|
|
833
|
+
# Tìm function call pattern: name(...) hoặc $name(...)
|
|
834
|
+
match = re.match(r'^(\$?[a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', expr, re.DOTALL)
|
|
835
|
+
if match:
|
|
836
|
+
params_string = match.group(2).strip()
|
|
837
|
+
if not params_string:
|
|
838
|
+
return False
|
|
839
|
+
|
|
840
|
+
# Đếm số function calls trong params (không tính function call ngoài cùng)
|
|
841
|
+
# Pattern: tên hàm + ( (có thể có $ prefix)
|
|
842
|
+
nested_pattern = r'\$?[a-zA-Z_][a-zA-Z0-9_]*\s*\('
|
|
843
|
+
matches = re.findall(nested_pattern, params_string)
|
|
844
|
+
# Nếu có nhiều hơn 0 function calls trong params → có nested calls
|
|
845
|
+
return len(matches) > 0
|
|
846
|
+
|
|
847
|
+
return False
|
|
848
|
+
|
|
849
|
+
def _is_function_call_without_dollar(self, expr):
|
|
850
|
+
"""
|
|
851
|
+
Kiểm tra xem có phải là function call không có $ prefix và KHÔNG phải state variable
|
|
852
|
+
Tất cả function calls không có $ prefix → luôn dùng object handler
|
|
853
|
+
Ví dụ: test(), handleClick(Event), nestedCall(...) → True (handler function)
|
|
854
|
+
Ví dụ: $test(), $press++, setCount(...) → False (có $ hoặc biểu thức)
|
|
855
|
+
Ví dụ: count(...) nếu count có trong usestate_variables → False (state variable, không phải handler)
|
|
856
|
+
"""
|
|
857
|
+
expr = expr.strip()
|
|
858
|
+
|
|
859
|
+
# Match function call pattern: name(...)
|
|
860
|
+
match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(', expr, re.DOTALL)
|
|
861
|
+
if match:
|
|
862
|
+
func_name = match.group(1)
|
|
863
|
+
# Không có $ prefix
|
|
864
|
+
if not func_name.startswith('$'):
|
|
865
|
+
# Kiểm tra xem có phải là state variable không (có trong usestate_variables)
|
|
866
|
+
# Nếu là state variable → không phải handler function → xử lý như biểu thức
|
|
867
|
+
if func_name in self.usestate_variables:
|
|
868
|
+
return False # State variable → xử lý như biểu thức
|
|
869
|
+
|
|
870
|
+
# Tất cả function calls không có $ prefix → luôn là handler function (dùng object handler)
|
|
871
|
+
return True
|
|
872
|
+
|
|
873
|
+
# Match simple function name: name
|
|
874
|
+
match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)$', expr)
|
|
875
|
+
if match:
|
|
876
|
+
func_name = match.group(1)
|
|
877
|
+
if not func_name.startswith('$'):
|
|
878
|
+
# Kiểm tra xem có phải là state variable không
|
|
879
|
+
if func_name in self.usestate_variables:
|
|
880
|
+
return False # State variable → xử lý như biểu thức
|
|
881
|
+
return True
|
|
882
|
+
|
|
883
|
+
return False
|
|
884
|
+
|
|
885
|
+
def _process_expression_to_arrow(self, expr):
|
|
886
|
+
"""
|
|
887
|
+
Xử lý biểu thức thành arrow function format
|
|
888
|
+
- Nếu có $ prefix và có useState → dùng (event) => setStateKey(...)
|
|
889
|
+
- Nếu không → dùng (event) => ... hoặc () => ...
|
|
890
|
+
"""
|
|
891
|
+
original_expr = expr.strip()
|
|
892
|
+
|
|
893
|
+
# Convert PHP variable to JavaScript
|
|
894
|
+
expr_js = self.convert_php_variable_to_js(original_expr)
|
|
895
|
+
|
|
896
|
+
# Xử lý Event parameters
|
|
897
|
+
expr_js = self.process_event_in_string(expr_js)
|
|
898
|
+
|
|
899
|
+
# Xử lý @attr, @prop, @val, @value
|
|
900
|
+
expr_js = self.process_attr_prop_in_string(expr_js, '@attr', '#ATTR')
|
|
901
|
+
expr_js = self.process_attr_prop_in_string(expr_js, '@prop', '#PROP')
|
|
902
|
+
expr_js = self.process_attr_prop_in_string(expr_js, '@val', '#VALUE')
|
|
903
|
+
expr_js = self.process_attr_prop_in_string(expr_js, '@value', '#VALUE')
|
|
904
|
+
|
|
905
|
+
# Convert PHP array syntax to JavaScript object syntax
|
|
906
|
+
expr_js = self.convert_php_array_to_js_object(expr_js)
|
|
907
|
+
|
|
908
|
+
# Kiểm tra xem có phải là function call không có $ prefix không
|
|
909
|
+
# Ví dụ: setCount($count - 1) → setCount(count - 1)
|
|
910
|
+
# Ví dụ: count(...) nếu count có trong usestate_variables → cần dùng setter
|
|
911
|
+
match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', original_expr, re.DOTALL)
|
|
912
|
+
if match:
|
|
913
|
+
func_name = match.group(1)
|
|
914
|
+
params_str = match.group(2).strip()
|
|
915
|
+
|
|
916
|
+
# Nếu là state variable (có trong usestate_variables), cần dùng setter
|
|
917
|
+
if func_name in self.usestate_variables:
|
|
918
|
+
# Parse params - nếu param là function call (không phải state variable) → dùng object config
|
|
919
|
+
params = self.split_by_comma(params_str)
|
|
920
|
+
processed_params = []
|
|
921
|
+
for param in params:
|
|
922
|
+
param = param.strip()
|
|
923
|
+
# Kiểm tra xem có phải là function call không
|
|
924
|
+
if self._is_function_call_in_param(param):
|
|
925
|
+
# Function call → parse thành object config
|
|
926
|
+
handler = self._parse_function_call_in_param(param)
|
|
927
|
+
if handler:
|
|
928
|
+
# Build object config string
|
|
929
|
+
processed_handler_params = []
|
|
930
|
+
for p in handler['params']:
|
|
931
|
+
processed_handler_params.append(self.process_parameter(p.strip(), in_params_context=True))
|
|
932
|
+
params_str_inner = ','.join(processed_handler_params)
|
|
933
|
+
handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str_inner}]}}'
|
|
934
|
+
processed_params.append(handler_str)
|
|
935
|
+
continue
|
|
936
|
+
|
|
937
|
+
# Không phải function call → xử lý như bình thường
|
|
938
|
+
param_js = self.convert_php_variable_to_js(param)
|
|
939
|
+
param_js = self.process_event_in_string(param_js)
|
|
940
|
+
param_js = self.process_attr_prop_in_string(param_js, '@attr', '#ATTR')
|
|
941
|
+
param_js = self.process_attr_prop_in_string(param_js, '@prop', '#PROP')
|
|
942
|
+
param_js = self.process_attr_prop_in_string(param_js, '@val', '#VALUE')
|
|
943
|
+
param_js = self.process_attr_prop_in_string(param_js, '@value', '#VALUE')
|
|
944
|
+
param_js = self.convert_php_array_to_js_object(param_js)
|
|
945
|
+
# Thay thế @EVENT thành "@EVENT" nếu chưa có quotes
|
|
946
|
+
param_js = re.sub(r'(?<!")@EVENT(?!")', '"@EVENT"', param_js)
|
|
947
|
+
processed_params.append(param_js)
|
|
948
|
+
|
|
949
|
+
params_str_js = ', '.join(processed_params)
|
|
950
|
+
# If func_name already starts with "set", use it as is (it's already a setter name)
|
|
951
|
+
# Otherwise, capitalize first letter: count -> setCount
|
|
952
|
+
if func_name.startswith('set') and len(func_name) > 3:
|
|
953
|
+
setter_name = func_name # Already a setter name like "setCount"
|
|
954
|
+
else:
|
|
955
|
+
setter_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
|
|
956
|
+
return f'(event) => {setter_name}({params_str_js})'
|
|
957
|
+
|
|
958
|
+
# Kiểm tra xem có phải là function call với $ prefix không
|
|
959
|
+
# Ví dụ: $test($a+1) → test(a + 1)
|
|
960
|
+
# Ví dụ: $setCount($count + 1) → setCount(count + 1)
|
|
961
|
+
match = re.match(r'^\$([a-zA-Z_][a-zA-Z0-9_]*)\s*\((.*)\)$', original_expr, re.DOTALL)
|
|
962
|
+
if match:
|
|
963
|
+
func_name = match.group(1)
|
|
964
|
+
params_str = match.group(2).strip()
|
|
965
|
+
|
|
966
|
+
# Kiểm tra xem có phải là state variable hoặc setter không (có trong usestate_variables)
|
|
967
|
+
# Nếu là state variable hoặc setter → cần dùng setter
|
|
968
|
+
if func_name in self.usestate_variables:
|
|
969
|
+
# Có useState → dùng (event) => setStateKey(...)
|
|
970
|
+
# Parse params - nếu param là function call (không phải state variable) → dùng object config
|
|
971
|
+
params = self.split_by_comma(params_str)
|
|
972
|
+
processed_params = []
|
|
973
|
+
for param in params:
|
|
974
|
+
param = param.strip()
|
|
975
|
+
# Kiểm tra xem có phải là function call không
|
|
976
|
+
if self._is_function_call_in_param(param):
|
|
977
|
+
# Function call → parse thành object config
|
|
978
|
+
handler = self._parse_function_call_in_param(param)
|
|
979
|
+
if handler:
|
|
980
|
+
# Build object config string
|
|
981
|
+
processed_handler_params = []
|
|
982
|
+
for p in handler['params']:
|
|
983
|
+
processed_handler_params.append(self.process_parameter(p.strip(), in_params_context=True))
|
|
984
|
+
params_str_inner = ','.join(processed_handler_params)
|
|
985
|
+
handler_str = f'{{"handler":"{handler["handler"]}","params":[{params_str_inner}]}}'
|
|
986
|
+
processed_params.append(handler_str)
|
|
987
|
+
continue
|
|
988
|
+
|
|
989
|
+
# Không phải function call → xử lý như bình thường
|
|
990
|
+
param_js = self.convert_php_variable_to_js(param)
|
|
991
|
+
# Xử lý Event parameters
|
|
992
|
+
param_js = self.process_event_in_string(param_js)
|
|
993
|
+
# Chuẩn hóa @event thành @EVENT
|
|
994
|
+
param_js = re.sub(r'@(?:event|Event|EVENT)(?![a-zA-Z])', '@EVENT', param_js, flags=re.IGNORECASE)
|
|
995
|
+
# Xử lý @attr, @prop, @val, @value
|
|
996
|
+
param_js = self.process_attr_prop_in_string(param_js, '@attr', '#ATTR')
|
|
997
|
+
param_js = self.process_attr_prop_in_string(param_js, '@prop', '#PROP')
|
|
998
|
+
param_js = self.process_attr_prop_in_string(param_js, '@val', '#VALUE')
|
|
999
|
+
param_js = self.process_attr_prop_in_string(param_js, '@value', '#VALUE')
|
|
1000
|
+
# Convert PHP array syntax to JavaScript object syntax
|
|
1001
|
+
param_js = self.convert_php_array_to_js_object(param_js)
|
|
1002
|
+
# Thay thế @EVENT thành "@EVENT" nếu chưa có quotes
|
|
1003
|
+
param_js = re.sub(r'(?<!")@EVENT(?!")', '"@EVENT"', param_js)
|
|
1004
|
+
processed_params.append(param_js)
|
|
1005
|
+
|
|
1006
|
+
params_str_js = ', '.join(processed_params)
|
|
1007
|
+
# If func_name already starts with "set", use it as is (it's already a setter name)
|
|
1008
|
+
# Otherwise, capitalize first letter: count -> setCount
|
|
1009
|
+
if func_name.startswith('set') and len(func_name) > 3:
|
|
1010
|
+
setter_name = func_name # Already a setter name like "setCount"
|
|
1011
|
+
else:
|
|
1012
|
+
setter_name = f'set{func_name[0].upper()}{func_name[1:]}' if func_name and len(func_name) > 0 else f'set{func_name}'
|
|
1013
|
+
return f'(event) => {setter_name}({params_str_js})'
|
|
1014
|
+
else:
|
|
1015
|
+
# Không có useState → dùng arrow function thông thường
|
|
1016
|
+
return f'(event) => {expr_js}'
|
|
1017
|
+
|
|
1018
|
+
# Kiểm tra xem có phải là biểu thức với $ variable không
|
|
1019
|
+
# Ví dụ: $press++, $hoverCount+=2
|
|
1020
|
+
if re.search(r'\$[a-zA-Z_][a-zA-Z0-9_]*', original_expr):
|
|
1021
|
+
# Có $ variable → kiểm tra useState
|
|
1022
|
+
# Extract variable name
|
|
1023
|
+
var_match = re.search(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', original_expr)
|
|
1024
|
+
if var_match:
|
|
1025
|
+
var_name = var_match.group(1)
|
|
1026
|
+
if var_name in self.usestate_variables:
|
|
1027
|
+
# Có useState → dùng (event) => setStateKey(...)
|
|
1028
|
+
# Convert expression: $press++ → press + 1
|
|
1029
|
+
# Hoặc giữ nguyên expression và wrap trong setter
|
|
1030
|
+
# Setter name format: setStateKey (capitalize first letter)
|
|
1031
|
+
setter_name = f'set{var_name[0].upper()}{var_name[1:]}' if var_name and len(var_name) > 0 else f'set{var_name}'
|
|
1032
|
+
# For assignment: $press++ → setPress(press + 1)
|
|
1033
|
+
# For simple increment: $press++ → setPress(press + 1)
|
|
1034
|
+
if '++' in expr_js:
|
|
1035
|
+
expr_js = expr_js.replace('++', '')
|
|
1036
|
+
return f'(event) => {setter_name}({expr_js} + 1)'
|
|
1037
|
+
elif '+=' in expr_js:
|
|
1038
|
+
# $hoverCount+=2 → setHoverCount(hoverCount + 2)
|
|
1039
|
+
parts = expr_js.split('+=')
|
|
1040
|
+
if len(parts) == 2:
|
|
1041
|
+
var = parts[0].strip()
|
|
1042
|
+
value = parts[1].strip()
|
|
1043
|
+
return f'(event) => {setter_name}({var} + {value})'
|
|
1044
|
+
elif '=' in expr_js and not expr_js.startswith('='):
|
|
1045
|
+
# $test = $hoverCount + 2 → setTest(hoverCount + 2)
|
|
1046
|
+
parts = expr_js.split('=', 1)
|
|
1047
|
+
if len(parts) == 2:
|
|
1048
|
+
var = parts[0].strip()
|
|
1049
|
+
value = parts[1].strip()
|
|
1050
|
+
# Setter name format: setStateKey (capitalize first letter)
|
|
1051
|
+
setter_name = f'set{var[0].upper()}{var[1:]}' if var and len(var) > 0 else f'set{var}'
|
|
1052
|
+
return f'(event) => {setter_name}({value})'
|
|
1053
|
+
else:
|
|
1054
|
+
# Other expression with useState variable
|
|
1055
|
+
return f'(event) => {setter_name}({expr_js})'
|
|
1056
|
+
|
|
1057
|
+
# Không có useState hoặc không phải $ variable → dùng arrow function thông thường
|
|
1058
|
+
# Kiểm tra xem có cần event parameter không
|
|
1059
|
+
if '@EVENT' in expr_js or 'event' in expr_js.lower():
|
|
1060
|
+
return f'(event) => {expr_js}'
|
|
1061
|
+
else:
|
|
1062
|
+
return f'() => {expr_js}'
|
|
1063
|
+
|
|
1064
|
+
def build_event_config_unified(self, event_type, handlers, arrow_functions):
|
|
1065
|
+
"""
|
|
1066
|
+
Build event config với unified format: arrow functions + handler objects
|
|
1067
|
+
Tất cả đều dùng __addEventConfig
|
|
1068
|
+
Ví dụ: @click($a++; $b--, test($a, $b))
|
|
1069
|
+
→ __addEventConfig('click', [() => a++, () => b--, {"handler": "test", "params": [a, b]}])
|
|
1070
|
+
"""
|
|
1071
|
+
handler_items = []
|
|
1072
|
+
|
|
1073
|
+
# Add arrow functions TRƯỚC
|
|
1074
|
+
handler_items.extend(arrow_functions)
|
|
1075
|
+
|
|
1076
|
+
# Add handlers (objects) SAU
|
|
1077
|
+
for handler in handlers:
|
|
1078
|
+
handler_name = handler['handler']
|
|
1079
|
+
params = handler['params']
|
|
1080
|
+
|
|
1081
|
+
# Process parameters
|
|
1082
|
+
processed_params = []
|
|
1083
|
+
for param in params:
|
|
1084
|
+
processed_params.append(self.process_parameter(param))
|
|
1085
|
+
|
|
1086
|
+
# Build handler object
|
|
1087
|
+
params_str = ','.join(processed_params)
|
|
1088
|
+
handler_str = f'{{"handler":"{handler_name}","params":[{params_str}]}}'
|
|
1089
|
+
handler_items.append(handler_str)
|
|
1090
|
+
|
|
1091
|
+
handlers_str = f'[{",".join(handler_items)}]'
|
|
1092
|
+
return f'this.__addEventConfig("{event_type}", {handlers_str})'
|
|
1093
|
+
|
|
1094
|
+
def build_event_config_mixed(self, event_type, handlers, quick_handles):
|
|
1095
|
+
"""
|
|
1096
|
+
Build event config với mixed format: quick_handles (arrow functions) + handlers (objects)
|
|
1097
|
+
DEPRECATED: Dùng build_event_config_unified thay thế
|
|
1098
|
+
"""
|
|
1099
|
+
return self.build_event_config_unified(event_type, handlers, quick_handles)
|