@hyperfixi/core 2.2.1 → 2.3.0

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.
@@ -456,7 +456,9 @@ function tokenize(input) {
456
456
  prevToken.value === ',' ||
457
457
  prevToken.value === ';';
458
458
  const isAdjacentToPrev = prevToken && prevToken.end === tokenizer.position;
459
- if (isCSSSelectorContext && !isAdjacentToPrev && isAlpha(peek(tokenizer))) {
459
+ if (isCSSSelectorContext &&
460
+ !isAdjacentToPrev &&
461
+ (isAlpha(peek(tokenizer)) || peek(tokenizer) === '{')) {
460
462
  tokenizeCSSSelector(tokenizer);
461
463
  continue;
462
464
  }
@@ -689,6 +691,17 @@ function tokenizeCSSSelector(tokenizer) {
689
691
  const start = tokenizer.position;
690
692
  const prefix = advance(tokenizer);
691
693
  let value = prefix;
694
+ if (tokenizer.position < tokenizer.input.length && tokenizer.input[tokenizer.position] === '{') {
695
+ value += advance(tokenizer);
696
+ while (tokenizer.position < tokenizer.input.length) {
697
+ const ch = tokenizer.input[tokenizer.position];
698
+ value += advance(tokenizer);
699
+ if (ch === '}')
700
+ break;
701
+ }
702
+ addToken(tokenizer, TokenKind.SELECTOR, value, start);
703
+ return;
704
+ }
692
705
  while (tokenizer.position < tokenizer.input.length) {
693
706
  const char = tokenizer.input[tokenizer.position];
694
707
  if (isAlphaNumeric(char) || char === '-' || char === '_' || char === ':') {
@@ -2379,33 +2392,14 @@ function parseTriggerCommand(ctx, identifierNode) {
2379
2392
  });
2380
2393
  }
2381
2394
  }
2382
- while (!isCommandBoundary(ctx)) {
2383
- allArgs.push(ctx.parsePrimary());
2384
- }
2385
- let operationIndex = -1;
2386
- let operationKeyword = 'on';
2387
- for (let i = 0; i < allArgs.length; i++) {
2388
- const arg = allArgs[i];
2389
- const argRecord = arg;
2390
- const argValue = argRecord.name || argRecord.value;
2391
- if ((arg.type === 'identifier' || arg.type === 'literal' || arg.type === 'keyword') &&
2392
- (argValue === 'on' || argValue === 'to')) {
2393
- operationIndex = i;
2394
- operationKeyword = argValue;
2395
- break;
2395
+ const finalArgs = [...allArgs];
2396
+ if (ctx.check('on') || ctx.check('to')) {
2397
+ const keyword = ctx.advance().value;
2398
+ finalArgs.push(ctx.createIdentifier(keyword));
2399
+ while (!isCommandBoundary(ctx)) {
2400
+ finalArgs.push(ctx.parsePrimary());
2396
2401
  }
2397
2402
  }
2398
- const finalArgs = [];
2399
- if (operationIndex === -1) {
2400
- finalArgs.push(...allArgs);
2401
- }
2402
- else {
2403
- const eventArgs = allArgs.slice(0, operationIndex);
2404
- const targetArgs = allArgs.slice(operationIndex + 1);
2405
- finalArgs.push(...eventArgs);
2406
- finalArgs.push(ctx.createIdentifier(operationKeyword));
2407
- finalArgs.push(...targetArgs);
2408
- }
2409
2403
  return CommandNodeBuilder.fromIdentifier(identifierNode)
2410
2404
  .withArgs(...finalArgs)
2411
2405
  .endingAt(ctx.getPosition())
@@ -3125,8 +3119,13 @@ function parseSwapCommand(ctx, identifierNode) {
3125
3119
 
3126
3120
  function parseWaitCommand(ctx, commandToken) {
3127
3121
  const args = [];
3128
- if (ctx.checkTimeExpression() || ctx.checkLiteral()) {
3129
- const timeExpr = ctx.parsePrimary();
3122
+ const isExpressionStart = ctx.checkTimeExpression() ||
3123
+ ctx.checkLiteral() ||
3124
+ ctx.checkContextVar() ||
3125
+ ctx.check('(') ||
3126
+ (ctx.checkIdentifierLike() && !ctx.check('for') && !isCommandBoundary(ctx));
3127
+ if (isExpressionStart) {
3128
+ const timeExpr = ctx.parseExpression();
3130
3129
  args.push(timeExpr);
3131
3130
  return CommandNodeBuilder.from(commandToken)
3132
3131
  .withArgs(...args)
@@ -3312,43 +3311,24 @@ function parsePropertyOfTarget(ctx, startPosition) {
3312
3311
  return null;
3313
3312
  const thePosition = ctx.savePosition();
3314
3313
  ctx.advance();
3315
- const nextToken = ctx.peek();
3316
- const tokenAfterNext = ctx.peekAt(1);
3317
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.OF) {
3318
- const propertyToken = ctx.advance();
3319
- if (ctx.check(KEYWORDS.OF)) {
3320
- ctx.advance();
3321
- const targetToken = ctx.advance();
3322
- const isIdSelector = targetToken.value.startsWith('#');
3323
- return {
3324
- type: 'propertyOfExpression',
3325
- property: {
3326
- type: 'identifier',
3327
- name: propertyToken.value,
3328
- start: propertyToken.start,
3329
- end: propertyToken.end,
3330
- },
3331
- target: {
3332
- type: isIdSelector ? 'idSelector' : 'cssSelector',
3333
- value: targetToken.value,
3334
- start: targetToken.start,
3335
- end: targetToken.end,
3336
- },
3337
- start: startPosition,
3338
- end: ctx.savePosition(),
3339
- };
3340
- }
3341
- ctx.restorePosition(startPosition);
3314
+ const propertyExpr = ctx.parseExpression();
3315
+ if (!propertyExpr) {
3316
+ ctx.restorePosition(thePosition);
3342
3317
  return null;
3343
3318
  }
3344
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.TO) {
3345
- const variableToken = ctx.advance();
3346
- return {
3347
- type: 'identifier',
3348
- name: variableToken.value,
3349
- start: variableToken.start,
3350
- end: variableToken.end,
3351
- };
3319
+ const exprAny = propertyExpr;
3320
+ if (exprAny.type === 'binaryExpression' && exprAny.operator === KEYWORDS.OF) {
3321
+ const property = exprAny.left;
3322
+ const target = exprAny.right;
3323
+ return createPropertyOfExpression(property, target, {
3324
+ start: property.start ?? 0,
3325
+ end: target.end ?? 0,
3326
+ line: property.line ?? 1,
3327
+ column: property.column ?? 1,
3328
+ });
3329
+ }
3330
+ if (ctx.check(KEYWORDS.TO)) {
3331
+ return propertyExpr;
3352
3332
  }
3353
3333
  ctx.restorePosition(thePosition);
3354
3334
  return null;
@@ -3707,6 +3687,71 @@ function parseFetchNakedNamedArgs(ctx) {
3707
3687
  column: startPos.column,
3708
3688
  };
3709
3689
  }
3690
+ function findJsEndBoundary(ctx, startPos) {
3691
+ const input = ctx.getInputSlice(startPos);
3692
+ if (!input) {
3693
+ return startPos;
3694
+ }
3695
+ let i = 0;
3696
+ while (i < input.length) {
3697
+ const ch = input[i];
3698
+ if (ch === "'" || ch === '\u2019' || ch === '\u2018') {
3699
+ i++;
3700
+ while (i < input.length && input[i] !== ch) {
3701
+ if (input[i] === '\\')
3702
+ i++;
3703
+ i++;
3704
+ }
3705
+ i++;
3706
+ continue;
3707
+ }
3708
+ if (ch === '"') {
3709
+ i++;
3710
+ while (i < input.length && input[i] !== '"') {
3711
+ if (input[i] === '\\')
3712
+ i++;
3713
+ i++;
3714
+ }
3715
+ i++;
3716
+ continue;
3717
+ }
3718
+ if (ch === '`') {
3719
+ i++;
3720
+ while (i < input.length && input[i] !== '`') {
3721
+ if (input[i] === '\\')
3722
+ i++;
3723
+ i++;
3724
+ }
3725
+ i++;
3726
+ continue;
3727
+ }
3728
+ if (ch === '/' && i + 1 < input.length && input[i + 1] === '/') {
3729
+ i += 2;
3730
+ while (i < input.length && input[i] !== '\n')
3731
+ i++;
3732
+ continue;
3733
+ }
3734
+ if (ch === '/' && i + 1 < input.length && input[i + 1] === '*') {
3735
+ i += 2;
3736
+ while (i < input.length &&
3737
+ !(input[i] === '*' && i + 1 < input.length && input[i + 1] === '/'))
3738
+ i++;
3739
+ i += 2;
3740
+ continue;
3741
+ }
3742
+ if ((ch === 'e' || ch === 'E') &&
3743
+ i + 3 <= input.length &&
3744
+ input.slice(i, i + 3).toLowerCase() === 'end') {
3745
+ const before = i === 0 || !/[a-zA-Z0-9_]/.test(input[i - 1]);
3746
+ const after = i + 3 >= input.length || !/[a-zA-Z0-9_]/.test(input[i + 3]);
3747
+ if (before && after) {
3748
+ return startPos + i;
3749
+ }
3750
+ }
3751
+ i++;
3752
+ }
3753
+ return startPos + input.length;
3754
+ }
3710
3755
  function parseJsCommand(ctx, identifierNode) {
3711
3756
  const parameters = [];
3712
3757
  if (ctx.match('(')) {
@@ -3719,11 +3764,12 @@ function parseJsCommand(ctx, identifierNode) {
3719
3764
  ctx.consume(')', 'Expected ) after js parameters');
3720
3765
  }
3721
3766
  const jsCodeStart = ctx.peek().start;
3722
- while (!ctx.check(KEYWORDS.END) && !ctx.isAtEnd()) {
3767
+ const jsCodeEnd = findJsEndBoundary(ctx, jsCodeStart);
3768
+ while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.END)) {
3769
+ if (ctx.peek().start >= jsCodeEnd)
3770
+ break;
3723
3771
  ctx.advance();
3724
3772
  }
3725
- const endToken = ctx.peek();
3726
- const jsCodeEnd = endToken.start;
3727
3773
  ctx.consume(KEYWORDS.END, 'Expected end after js code body');
3728
3774
  const rawSlice = ctx.getInputSlice(jsCodeStart, jsCodeEnd);
3729
3775
  const code = rawSlice.trim();
@@ -4626,7 +4672,12 @@ class Parser {
4626
4672
  }
4627
4673
  else {
4628
4674
  const commandName = expr.name.toLowerCase();
4629
- if (commandName === 'wait' && this.checkTimeExpression()) {
4675
+ if (commandName === 'wait' &&
4676
+ (this.checkTimeExpression() ||
4677
+ this.checkNumber() ||
4678
+ this.checkIdentifier() ||
4679
+ this.checkContextVar() ||
4680
+ this.check('('))) {
4630
4681
  const command = this.createCommandFromIdentifier(expr);
4631
4682
  if (command) {
4632
4683
  expr = command;
@@ -5845,9 +5896,7 @@ class Parser {
5845
5896
  while (!this.isAtEnd() && !this.check('end')) {
5846
5897
  if (this.match('on')) {
5847
5898
  const handlerPos = this.getPosition();
5848
- const eventToken = this.peek();
5849
- const eventName = eventToken.value;
5850
- this.advance();
5899
+ const eventName = this.parseEventNameWithNamespace("Expected event name after 'on'");
5851
5900
  const eventArgs = [];
5852
5901
  if (this.check('(')) {
5853
5902
  this.advance();
@@ -454,7 +454,9 @@ function tokenize(input) {
454
454
  prevToken.value === ',' ||
455
455
  prevToken.value === ';';
456
456
  const isAdjacentToPrev = prevToken && prevToken.end === tokenizer.position;
457
- if (isCSSSelectorContext && !isAdjacentToPrev && isAlpha(peek(tokenizer))) {
457
+ if (isCSSSelectorContext &&
458
+ !isAdjacentToPrev &&
459
+ (isAlpha(peek(tokenizer)) || peek(tokenizer) === '{')) {
458
460
  tokenizeCSSSelector(tokenizer);
459
461
  continue;
460
462
  }
@@ -687,6 +689,17 @@ function tokenizeCSSSelector(tokenizer) {
687
689
  const start = tokenizer.position;
688
690
  const prefix = advance(tokenizer);
689
691
  let value = prefix;
692
+ if (tokenizer.position < tokenizer.input.length && tokenizer.input[tokenizer.position] === '{') {
693
+ value += advance(tokenizer);
694
+ while (tokenizer.position < tokenizer.input.length) {
695
+ const ch = tokenizer.input[tokenizer.position];
696
+ value += advance(tokenizer);
697
+ if (ch === '}')
698
+ break;
699
+ }
700
+ addToken(tokenizer, TokenKind.SELECTOR, value, start);
701
+ return;
702
+ }
690
703
  while (tokenizer.position < tokenizer.input.length) {
691
704
  const char = tokenizer.input[tokenizer.position];
692
705
  if (isAlphaNumeric(char) || char === '-' || char === '_' || char === ':') {
@@ -2377,33 +2390,14 @@ function parseTriggerCommand(ctx, identifierNode) {
2377
2390
  });
2378
2391
  }
2379
2392
  }
2380
- while (!isCommandBoundary(ctx)) {
2381
- allArgs.push(ctx.parsePrimary());
2382
- }
2383
- let operationIndex = -1;
2384
- let operationKeyword = 'on';
2385
- for (let i = 0; i < allArgs.length; i++) {
2386
- const arg = allArgs[i];
2387
- const argRecord = arg;
2388
- const argValue = argRecord.name || argRecord.value;
2389
- if ((arg.type === 'identifier' || arg.type === 'literal' || arg.type === 'keyword') &&
2390
- (argValue === 'on' || argValue === 'to')) {
2391
- operationIndex = i;
2392
- operationKeyword = argValue;
2393
- break;
2393
+ const finalArgs = [...allArgs];
2394
+ if (ctx.check('on') || ctx.check('to')) {
2395
+ const keyword = ctx.advance().value;
2396
+ finalArgs.push(ctx.createIdentifier(keyword));
2397
+ while (!isCommandBoundary(ctx)) {
2398
+ finalArgs.push(ctx.parsePrimary());
2394
2399
  }
2395
2400
  }
2396
- const finalArgs = [];
2397
- if (operationIndex === -1) {
2398
- finalArgs.push(...allArgs);
2399
- }
2400
- else {
2401
- const eventArgs = allArgs.slice(0, operationIndex);
2402
- const targetArgs = allArgs.slice(operationIndex + 1);
2403
- finalArgs.push(...eventArgs);
2404
- finalArgs.push(ctx.createIdentifier(operationKeyword));
2405
- finalArgs.push(...targetArgs);
2406
- }
2407
2401
  return CommandNodeBuilder.fromIdentifier(identifierNode)
2408
2402
  .withArgs(...finalArgs)
2409
2403
  .endingAt(ctx.getPosition())
@@ -3123,8 +3117,13 @@ function parseSwapCommand(ctx, identifierNode) {
3123
3117
 
3124
3118
  function parseWaitCommand(ctx, commandToken) {
3125
3119
  const args = [];
3126
- if (ctx.checkTimeExpression() || ctx.checkLiteral()) {
3127
- const timeExpr = ctx.parsePrimary();
3120
+ const isExpressionStart = ctx.checkTimeExpression() ||
3121
+ ctx.checkLiteral() ||
3122
+ ctx.checkContextVar() ||
3123
+ ctx.check('(') ||
3124
+ (ctx.checkIdentifierLike() && !ctx.check('for') && !isCommandBoundary(ctx));
3125
+ if (isExpressionStart) {
3126
+ const timeExpr = ctx.parseExpression();
3128
3127
  args.push(timeExpr);
3129
3128
  return CommandNodeBuilder.from(commandToken)
3130
3129
  .withArgs(...args)
@@ -3310,43 +3309,24 @@ function parsePropertyOfTarget(ctx, startPosition) {
3310
3309
  return null;
3311
3310
  const thePosition = ctx.savePosition();
3312
3311
  ctx.advance();
3313
- const nextToken = ctx.peek();
3314
- const tokenAfterNext = ctx.peekAt(1);
3315
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.OF) {
3316
- const propertyToken = ctx.advance();
3317
- if (ctx.check(KEYWORDS.OF)) {
3318
- ctx.advance();
3319
- const targetToken = ctx.advance();
3320
- const isIdSelector = targetToken.value.startsWith('#');
3321
- return {
3322
- type: 'propertyOfExpression',
3323
- property: {
3324
- type: 'identifier',
3325
- name: propertyToken.value,
3326
- start: propertyToken.start,
3327
- end: propertyToken.end,
3328
- },
3329
- target: {
3330
- type: isIdSelector ? 'idSelector' : 'cssSelector',
3331
- value: targetToken.value,
3332
- start: targetToken.start,
3333
- end: targetToken.end,
3334
- },
3335
- start: startPosition,
3336
- end: ctx.savePosition(),
3337
- };
3338
- }
3339
- ctx.restorePosition(startPosition);
3312
+ const propertyExpr = ctx.parseExpression();
3313
+ if (!propertyExpr) {
3314
+ ctx.restorePosition(thePosition);
3340
3315
  return null;
3341
3316
  }
3342
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.TO) {
3343
- const variableToken = ctx.advance();
3344
- return {
3345
- type: 'identifier',
3346
- name: variableToken.value,
3347
- start: variableToken.start,
3348
- end: variableToken.end,
3349
- };
3317
+ const exprAny = propertyExpr;
3318
+ if (exprAny.type === 'binaryExpression' && exprAny.operator === KEYWORDS.OF) {
3319
+ const property = exprAny.left;
3320
+ const target = exprAny.right;
3321
+ return createPropertyOfExpression(property, target, {
3322
+ start: property.start ?? 0,
3323
+ end: target.end ?? 0,
3324
+ line: property.line ?? 1,
3325
+ column: property.column ?? 1,
3326
+ });
3327
+ }
3328
+ if (ctx.check(KEYWORDS.TO)) {
3329
+ return propertyExpr;
3350
3330
  }
3351
3331
  ctx.restorePosition(thePosition);
3352
3332
  return null;
@@ -3705,6 +3685,71 @@ function parseFetchNakedNamedArgs(ctx) {
3705
3685
  column: startPos.column,
3706
3686
  };
3707
3687
  }
3688
+ function findJsEndBoundary(ctx, startPos) {
3689
+ const input = ctx.getInputSlice(startPos);
3690
+ if (!input) {
3691
+ return startPos;
3692
+ }
3693
+ let i = 0;
3694
+ while (i < input.length) {
3695
+ const ch = input[i];
3696
+ if (ch === "'" || ch === '\u2019' || ch === '\u2018') {
3697
+ i++;
3698
+ while (i < input.length && input[i] !== ch) {
3699
+ if (input[i] === '\\')
3700
+ i++;
3701
+ i++;
3702
+ }
3703
+ i++;
3704
+ continue;
3705
+ }
3706
+ if (ch === '"') {
3707
+ i++;
3708
+ while (i < input.length && input[i] !== '"') {
3709
+ if (input[i] === '\\')
3710
+ i++;
3711
+ i++;
3712
+ }
3713
+ i++;
3714
+ continue;
3715
+ }
3716
+ if (ch === '`') {
3717
+ i++;
3718
+ while (i < input.length && input[i] !== '`') {
3719
+ if (input[i] === '\\')
3720
+ i++;
3721
+ i++;
3722
+ }
3723
+ i++;
3724
+ continue;
3725
+ }
3726
+ if (ch === '/' && i + 1 < input.length && input[i + 1] === '/') {
3727
+ i += 2;
3728
+ while (i < input.length && input[i] !== '\n')
3729
+ i++;
3730
+ continue;
3731
+ }
3732
+ if (ch === '/' && i + 1 < input.length && input[i + 1] === '*') {
3733
+ i += 2;
3734
+ while (i < input.length &&
3735
+ !(input[i] === '*' && i + 1 < input.length && input[i + 1] === '/'))
3736
+ i++;
3737
+ i += 2;
3738
+ continue;
3739
+ }
3740
+ if ((ch === 'e' || ch === 'E') &&
3741
+ i + 3 <= input.length &&
3742
+ input.slice(i, i + 3).toLowerCase() === 'end') {
3743
+ const before = i === 0 || !/[a-zA-Z0-9_]/.test(input[i - 1]);
3744
+ const after = i + 3 >= input.length || !/[a-zA-Z0-9_]/.test(input[i + 3]);
3745
+ if (before && after) {
3746
+ return startPos + i;
3747
+ }
3748
+ }
3749
+ i++;
3750
+ }
3751
+ return startPos + input.length;
3752
+ }
3708
3753
  function parseJsCommand(ctx, identifierNode) {
3709
3754
  const parameters = [];
3710
3755
  if (ctx.match('(')) {
@@ -3717,11 +3762,12 @@ function parseJsCommand(ctx, identifierNode) {
3717
3762
  ctx.consume(')', 'Expected ) after js parameters');
3718
3763
  }
3719
3764
  const jsCodeStart = ctx.peek().start;
3720
- while (!ctx.check(KEYWORDS.END) && !ctx.isAtEnd()) {
3765
+ const jsCodeEnd = findJsEndBoundary(ctx, jsCodeStart);
3766
+ while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.END)) {
3767
+ if (ctx.peek().start >= jsCodeEnd)
3768
+ break;
3721
3769
  ctx.advance();
3722
3770
  }
3723
- const endToken = ctx.peek();
3724
- const jsCodeEnd = endToken.start;
3725
3771
  ctx.consume(KEYWORDS.END, 'Expected end after js code body');
3726
3772
  const rawSlice = ctx.getInputSlice(jsCodeStart, jsCodeEnd);
3727
3773
  const code = rawSlice.trim();
@@ -4624,7 +4670,12 @@ class Parser {
4624
4670
  }
4625
4671
  else {
4626
4672
  const commandName = expr.name.toLowerCase();
4627
- if (commandName === 'wait' && this.checkTimeExpression()) {
4673
+ if (commandName === 'wait' &&
4674
+ (this.checkTimeExpression() ||
4675
+ this.checkNumber() ||
4676
+ this.checkIdentifier() ||
4677
+ this.checkContextVar() ||
4678
+ this.check('('))) {
4628
4679
  const command = this.createCommandFromIdentifier(expr);
4629
4680
  if (command) {
4630
4681
  expr = command;
@@ -5843,9 +5894,7 @@ class Parser {
5843
5894
  while (!this.isAtEnd() && !this.check('end')) {
5844
5895
  if (this.match('on')) {
5845
5896
  const handlerPos = this.getPosition();
5846
- const eventToken = this.peek();
5847
- const eventName = eventToken.value;
5848
- this.advance();
5897
+ const eventName = this.parseEventNameWithNamespace("Expected event name after 'on'");
5849
5898
  const eventArgs = [];
5850
5899
  if (this.check('(')) {
5851
5900
  this.advance();
@@ -1647,6 +1647,8 @@ class BaseExpressionEvaluator {
1647
1647
  return this.evaluateIdSelector(node, context);
1648
1648
  case 'attributeAccess':
1649
1649
  return this.evaluateAttributeAccess(node, context);
1650
+ case 'asExpression':
1651
+ return this.evaluateAsExpression(node, context);
1650
1652
  default:
1651
1653
  throw new Error(`Unsupported AST node type for evaluation: ${node.type}`);
1652
1654
  }
@@ -1932,6 +1934,14 @@ class BaseExpressionEvaluator {
1932
1934
  }
1933
1935
  return `@${node.attributeName}`;
1934
1936
  }
1937
+ async evaluateAsExpression(node, context) {
1938
+ const value = await this.evaluate(node.expression, context);
1939
+ const asExpr = this.expressionRegistry.get('as');
1940
+ if (asExpr) {
1941
+ return asExpr.evaluate(context, value, node.targetType);
1942
+ }
1943
+ throw new Error(`Conversion type 'as' not registered`);
1944
+ }
1935
1945
  getAvailableExpressions() {
1936
1946
  return Array.from(this.expressionRegistry.keys());
1937
1947
  }
@@ -1645,6 +1645,8 @@ class BaseExpressionEvaluator {
1645
1645
  return this.evaluateIdSelector(node, context);
1646
1646
  case 'attributeAccess':
1647
1647
  return this.evaluateAttributeAccess(node, context);
1648
+ case 'asExpression':
1649
+ return this.evaluateAsExpression(node, context);
1648
1650
  default:
1649
1651
  throw new Error(`Unsupported AST node type for evaluation: ${node.type}`);
1650
1652
  }
@@ -1930,6 +1932,14 @@ class BaseExpressionEvaluator {
1930
1932
  }
1931
1933
  return `@${node.attributeName}`;
1932
1934
  }
1935
+ async evaluateAsExpression(node, context) {
1936
+ const value = await this.evaluate(node.expression, context);
1937
+ const asExpr = this.expressionRegistry.get('as');
1938
+ if (asExpr) {
1939
+ return asExpr.evaluate(context, value, node.targetType);
1940
+ }
1941
+ throw new Error(`Conversion type 'as' not registered`);
1942
+ }
1933
1943
  getAvailableExpressions() {
1934
1944
  return Array.from(this.expressionRegistry.keys());
1935
1945
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfixi/core",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Multilingual, tree-shakeable hyperscript",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",