@mintjamsinc/ichigojs 0.1.55 → 0.1.56

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.
@@ -6731,11 +6731,18 @@ class ExpressionUtils {
6731
6731
  * Extracts variable and function names used in the expression.
6732
6732
  * @param expression The expression string to analyze.
6733
6733
  * @param functionDependencies A dictionary mapping function names to their dependencies.
6734
+ * @param options Optional parsing options.
6735
+ * - asScript: If true, parse the input as a Script (allows multi-statement source with semicolons,
6736
+ * declarations, control-flow, etc.). If false/omitted, parse as a single expression (the default,
6737
+ * for backward compatibility with interpolation and binding directives).
6734
6738
  * @returns An array of identifier names.
6735
6739
  */
6736
- static extractIdentifiers(expression, functionDependencies) {
6740
+ static extractIdentifiers(expression, functionDependencies, options) {
6737
6741
  const identifiers = new Set();
6738
- const ast = parse(`(${expression})`, { ecmaVersion: "latest" });
6742
+ // In expression mode we wrap in parens so acorn parses the source as a single expression.
6743
+ // In script mode we parse as a Program so that multi-statement bodies (e.g. "a=1; b=2") work.
6744
+ const source = options?.asScript ? expression : `(${expression})`;
6745
+ const ast = parse(source, { ecmaVersion: "latest" });
6739
6746
  // Use walk.full instead of walk.simple to visit ALL nodes including assignment LHS
6740
6747
  full(ast, (node) => {
6741
6748
  if (node.type === 'Identifier') {
@@ -6967,10 +6974,10 @@ class ExpressionUtils {
6967
6974
  * @param identifiers The list of identifiers that are available in bindings.
6968
6975
  * @returns The rewritten expression.
6969
6976
  */
6970
- static rewriteExpression(expression, identifiers) {
6977
+ static rewriteExpression(expression, identifiers, options) {
6971
6978
  // Reserved words and built-in objects that should not be prefixed with 'this.'
6972
6979
  const reserved = new Set([
6973
- 'event', '$ctx', '$newValue',
6980
+ 'event', '$event', '$ctx', '$newValue',
6974
6981
  'true', 'false', 'null', 'undefined', 'NaN', 'Infinity',
6975
6982
  'Math', 'Date', 'String', 'Number', 'Boolean', 'Array', 'Object',
6976
6983
  'JSON', 'console', 'window', 'document', 'navigator',
@@ -6982,7 +6989,7 @@ class ExpressionUtils {
6982
6989
  // identifiers that are used (right-hand side), not assigned to (left-hand side)
6983
6990
  let allIdentifiersInExpression;
6984
6991
  try {
6985
- allIdentifiersInExpression = ExpressionUtils.extractIdentifiers(expression, {});
6992
+ allIdentifiersInExpression = ExpressionUtils.extractIdentifiers(expression, {}, options);
6986
6993
  }
6987
6994
  catch (error) {
6988
6995
  console.warn('[ichigo.js] Failed to extract identifiers from expression:', expression, error);
@@ -7004,7 +7011,26 @@ class ExpressionUtils {
7004
7011
  try {
7005
7012
  // Build a map of positions to replace: { start: number, end: number, name: string }[]
7006
7013
  const replacements = [];
7007
- const parsedAst = parse(`(${expression})`, { ecmaVersion: 'latest' });
7014
+ // In script mode we must not wrap in parens (that would make multi-statement input invalid).
7015
+ // Offsets from the parser therefore refer directly to the original expression, so no shift.
7016
+ const asScript = options?.asScript === true;
7017
+ const source = asScript ? expression : `(${expression})`;
7018
+ const offsetShift = asScript ? 0 : 1;
7019
+ const parsedAst = parse(source, { ecmaVersion: 'latest' });
7020
+ // Track identifiers that are locally declared within the handler body (let/const/var, function
7021
+ // params) so we don't rewrite them to `this.xxx`. Only relevant in script mode, where the user
7022
+ // can write declarations; in expression mode there are no declarations to track.
7023
+ const locallyDeclared = new Set();
7024
+ if (asScript) {
7025
+ full(parsedAst, (node) => {
7026
+ if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier') {
7027
+ locallyDeclared.add(node.id.name);
7028
+ }
7029
+ else if (node.type === 'FunctionDeclaration' && node.id?.type === 'Identifier') {
7030
+ locallyDeclared.add(node.id.name);
7031
+ }
7032
+ });
7033
+ }
7008
7034
  // Collect all identifier nodes that should be replaced
7009
7035
  // Use walk.fullAncestor to visit ALL nodes (including assignment LHS) while tracking ancestors
7010
7036
  fullAncestor(parsedAst, (node, _state, ancestors) => {
@@ -7015,6 +7041,10 @@ class ExpressionUtils {
7015
7041
  if (!bindingIdentifiers.has(node.name)) {
7016
7042
  return;
7017
7043
  }
7044
+ // Skip identifiers that were declared locally in the handler body
7045
+ if (locallyDeclared.has(node.name)) {
7046
+ return;
7047
+ }
7018
7048
  // Check if this identifier is a property of a MemberExpression
7019
7049
  // (e.g., in 'obj.prop', we should skip 'prop')
7020
7050
  if (ancestors.length >= 1) {
@@ -7026,10 +7056,10 @@ class ExpressionUtils {
7026
7056
  }
7027
7057
  }
7028
7058
  }
7029
- // Add to replacements list (adjust for the wrapping parentheses)
7059
+ // Add to replacements list (adjust for the wrapping parentheses in expression mode)
7030
7060
  replacements.push({
7031
- start: node.start - 1,
7032
- end: node.end - 1,
7061
+ start: node.start - offsetShift,
7062
+ end: node.end - offsetShift,
7033
7063
  name: node.name
7034
7064
  });
7035
7065
  });
@@ -11228,10 +11258,12 @@ class VOnDirective {
11228
11258
  this.#eventName = parts[0];
11229
11259
  parts.slice(1).forEach(mod => this.#modifiers.add(mod));
11230
11260
  }
11231
- // Parse the expression to extract identifiers and create the handler wrapper
11261
+ // Parse the expression to extract identifiers and create the handler wrapper.
11262
+ // Event handlers are parsed in script mode so that users can write multi-statement bodies
11263
+ // (e.g. "a=1; b=2"), declarations, and control-flow constructs — matching Vue semantics.
11232
11264
  const expression = context.attribute.value;
11233
11265
  if (expression) {
11234
- this.#dependentIdentifiers = ExpressionUtils.extractIdentifiers(expression, context.vNode.vApplication.functionDependencies);
11266
+ this.#dependentIdentifiers = ExpressionUtils.extractIdentifiers(expression, context.vNode.vApplication.functionDependencies, { asScript: true });
11235
11267
  }
11236
11268
  // Check if this is a lifecycle hook or a regular event
11237
11269
  if (this.#eventName && this.#isLifecycleHook(this.#eventName)) {
@@ -11445,10 +11477,11 @@ class VOnDirective {
11445
11477
  // This allows the method to access the DOM element, VNode, and userData
11446
11478
  return originalMethod($ctx);
11447
11479
  }
11448
- // For inline expressions, rewrite to use 'this' context
11449
- // This allows assignments like "currentTab = 'shop'" to work correctly
11450
- const rewrittenExpr = this.#rewriteExpression(expression, identifiers);
11451
- const funcBody = `return (${rewrittenExpr});`;
11480
+ // For inline bodies, rewrite to use 'this' context
11481
+ // This allows assignments like "currentTab = 'shop'" to work correctly.
11482
+ // Script mode allows multi-statement bodies (e.g. "a=1; init()") and control-flow.
11483
+ const rewrittenExpr = this.#rewriteExpression(expression, identifiers, { asScript: true });
11484
+ const funcBody = rewrittenExpr;
11452
11485
  const func = new Function('$ctx', funcBody);
11453
11486
  return func.call(bindings?.raw, $ctx);
11454
11487
  };
@@ -11478,12 +11511,15 @@ class VOnDirective {
11478
11511
  // Pass event as first argument and $ctx as second argument
11479
11512
  return originalMethod(event, $ctx);
11480
11513
  }
11481
- // For inline expressions, rewrite to use 'this' context
11482
- // This allows assignments like "currentTab = 'shop'" to work correctly
11483
- const rewrittenExpr = this.#rewriteExpression(expression, identifiers);
11484
- const funcBody = `return (${rewrittenExpr});`;
11485
- const func = new Function('event', '$ctx', funcBody);
11486
- return func.call(bindings?.raw, event, $ctx);
11514
+ // For inline bodies, rewrite to use 'this' context
11515
+ // This allows assignments like "currentTab = 'shop'" to work correctly.
11516
+ // Script mode allows multi-statement bodies (e.g. "a=1; b=2") and control-flow,
11517
+ // so we emit the rewritten source directly as the function body (no `return (...)`).
11518
+ const rewrittenExpr = this.#rewriteExpression(expression, identifiers, { asScript: true });
11519
+ const funcBody = rewrittenExpr;
11520
+ // '$event' is an alias for 'event' for Vue compatibility
11521
+ const func = new Function('event', '$event', '$ctx', funcBody);
11522
+ return func.call(bindings?.raw, event, event, $ctx);
11487
11523
  };
11488
11524
  }
11489
11525
  /**
@@ -11494,8 +11530,8 @@ class VOnDirective {
11494
11530
  * @param identifiers The list of identifiers that are available in bindings.
11495
11531
  * @returns The rewritten expression.
11496
11532
  */
11497
- #rewriteExpression(expression, identifiers) {
11498
- return ExpressionUtils.rewriteExpression(expression, identifiers);
11533
+ #rewriteExpression(expression, identifiers, options) {
11534
+ return ExpressionUtils.rewriteExpression(expression, identifiers, options);
11499
11535
  }
11500
11536
  }
11501
11537