@hyperfixi/core 2.2.0 → 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.
@@ -455,7 +455,10 @@ function tokenize(input) {
455
455
  prevToken.value === '{' ||
456
456
  prevToken.value === ',' ||
457
457
  prevToken.value === ';';
458
- if (isCSSSelectorContext && isAlpha(peek(tokenizer))) {
458
+ const isAdjacentToPrev = prevToken && prevToken.end === tokenizer.position;
459
+ if (isCSSSelectorContext &&
460
+ !isAdjacentToPrev &&
461
+ (isAlpha(peek(tokenizer)) || peek(tokenizer) === '{')) {
459
462
  tokenizeCSSSelector(tokenizer);
460
463
  continue;
461
464
  }
@@ -688,6 +691,17 @@ function tokenizeCSSSelector(tokenizer) {
688
691
  const start = tokenizer.position;
689
692
  const prefix = advance(tokenizer);
690
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
+ }
691
705
  while (tokenizer.position < tokenizer.input.length) {
692
706
  const char = tokenizer.input[tokenizer.position];
693
707
  if (isAlphaNumeric(char) || char === '-' || char === '_' || char === ':') {
@@ -2378,33 +2392,14 @@ function parseTriggerCommand(ctx, identifierNode) {
2378
2392
  });
2379
2393
  }
2380
2394
  }
2381
- while (!isCommandBoundary(ctx)) {
2382
- allArgs.push(ctx.parsePrimary());
2383
- }
2384
- let operationIndex = -1;
2385
- let operationKeyword = 'on';
2386
- for (let i = 0; i < allArgs.length; i++) {
2387
- const arg = allArgs[i];
2388
- const argRecord = arg;
2389
- const argValue = argRecord.name || argRecord.value;
2390
- if ((arg.type === 'identifier' || arg.type === 'literal' || arg.type === 'keyword') &&
2391
- (argValue === 'on' || argValue === 'to')) {
2392
- operationIndex = i;
2393
- operationKeyword = argValue;
2394
- 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());
2395
2401
  }
2396
2402
  }
2397
- const finalArgs = [];
2398
- if (operationIndex === -1) {
2399
- finalArgs.push(...allArgs);
2400
- }
2401
- else {
2402
- const eventArgs = allArgs.slice(0, operationIndex);
2403
- const targetArgs = allArgs.slice(operationIndex + 1);
2404
- finalArgs.push(...eventArgs);
2405
- finalArgs.push(ctx.createIdentifier(operationKeyword));
2406
- finalArgs.push(...targetArgs);
2407
- }
2408
2403
  return CommandNodeBuilder.fromIdentifier(identifierNode)
2409
2404
  .withArgs(...finalArgs)
2410
2405
  .endingAt(ctx.getPosition())
@@ -3124,8 +3119,13 @@ function parseSwapCommand(ctx, identifierNode) {
3124
3119
 
3125
3120
  function parseWaitCommand(ctx, commandToken) {
3126
3121
  const args = [];
3127
- if (ctx.checkTimeExpression() || ctx.checkLiteral()) {
3128
- 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();
3129
3129
  args.push(timeExpr);
3130
3130
  return CommandNodeBuilder.from(commandToken)
3131
3131
  .withArgs(...args)
@@ -3311,43 +3311,24 @@ function parsePropertyOfTarget(ctx, startPosition) {
3311
3311
  return null;
3312
3312
  const thePosition = ctx.savePosition();
3313
3313
  ctx.advance();
3314
- const nextToken = ctx.peek();
3315
- const tokenAfterNext = ctx.peekAt(1);
3316
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.OF) {
3317
- const propertyToken = ctx.advance();
3318
- if (ctx.check(KEYWORDS.OF)) {
3319
- ctx.advance();
3320
- const targetToken = ctx.advance();
3321
- const isIdSelector = targetToken.value.startsWith('#');
3322
- return {
3323
- type: 'propertyOfExpression',
3324
- property: {
3325
- type: 'identifier',
3326
- name: propertyToken.value,
3327
- start: propertyToken.start,
3328
- end: propertyToken.end,
3329
- },
3330
- target: {
3331
- type: isIdSelector ? 'idSelector' : 'cssSelector',
3332
- value: targetToken.value,
3333
- start: targetToken.start,
3334
- end: targetToken.end,
3335
- },
3336
- start: startPosition,
3337
- end: ctx.savePosition(),
3338
- };
3339
- }
3340
- ctx.restorePosition(startPosition);
3314
+ const propertyExpr = ctx.parseExpression();
3315
+ if (!propertyExpr) {
3316
+ ctx.restorePosition(thePosition);
3341
3317
  return null;
3342
3318
  }
3343
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.TO) {
3344
- const variableToken = ctx.advance();
3345
- return {
3346
- type: 'identifier',
3347
- name: variableToken.value,
3348
- start: variableToken.start,
3349
- end: variableToken.end,
3350
- };
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;
3351
3332
  }
3352
3333
  ctx.restorePosition(thePosition);
3353
3334
  return null;
@@ -3706,6 +3687,71 @@ function parseFetchNakedNamedArgs(ctx) {
3706
3687
  column: startPos.column,
3707
3688
  };
3708
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
+ }
3709
3755
  function parseJsCommand(ctx, identifierNode) {
3710
3756
  const parameters = [];
3711
3757
  if (ctx.match('(')) {
@@ -3718,11 +3764,12 @@ function parseJsCommand(ctx, identifierNode) {
3718
3764
  ctx.consume(')', 'Expected ) after js parameters');
3719
3765
  }
3720
3766
  const jsCodeStart = ctx.peek().start;
3721
- 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;
3722
3771
  ctx.advance();
3723
3772
  }
3724
- const endToken = ctx.peek();
3725
- const jsCodeEnd = endToken.start;
3726
3773
  ctx.consume(KEYWORDS.END, 'Expected end after js code body');
3727
3774
  const rawSlice = ctx.getInputSlice(jsCodeStart, jsCodeEnd);
3728
3775
  const code = rawSlice.trim();
@@ -4625,7 +4672,12 @@ class Parser {
4625
4672
  }
4626
4673
  else {
4627
4674
  const commandName = expr.name.toLowerCase();
4628
- if (commandName === 'wait' && this.checkTimeExpression()) {
4675
+ if (commandName === 'wait' &&
4676
+ (this.checkTimeExpression() ||
4677
+ this.checkNumber() ||
4678
+ this.checkIdentifier() ||
4679
+ this.checkContextVar() ||
4680
+ this.check('('))) {
4629
4681
  const command = this.createCommandFromIdentifier(expr);
4630
4682
  if (command) {
4631
4683
  expr = command;
@@ -5844,9 +5896,7 @@ class Parser {
5844
5896
  while (!this.isAtEnd() && !this.check('end')) {
5845
5897
  if (this.match('on')) {
5846
5898
  const handlerPos = this.getPosition();
5847
- const eventToken = this.peek();
5848
- const eventName = eventToken.value;
5849
- this.advance();
5899
+ const eventName = this.parseEventNameWithNamespace("Expected event name after 'on'");
5850
5900
  const eventArgs = [];
5851
5901
  if (this.check('(')) {
5852
5902
  this.advance();
@@ -453,7 +453,10 @@ function tokenize(input) {
453
453
  prevToken.value === '{' ||
454
454
  prevToken.value === ',' ||
455
455
  prevToken.value === ';';
456
- if (isCSSSelectorContext && isAlpha(peek(tokenizer))) {
456
+ const isAdjacentToPrev = prevToken && prevToken.end === tokenizer.position;
457
+ if (isCSSSelectorContext &&
458
+ !isAdjacentToPrev &&
459
+ (isAlpha(peek(tokenizer)) || peek(tokenizer) === '{')) {
457
460
  tokenizeCSSSelector(tokenizer);
458
461
  continue;
459
462
  }
@@ -686,6 +689,17 @@ function tokenizeCSSSelector(tokenizer) {
686
689
  const start = tokenizer.position;
687
690
  const prefix = advance(tokenizer);
688
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
+ }
689
703
  while (tokenizer.position < tokenizer.input.length) {
690
704
  const char = tokenizer.input[tokenizer.position];
691
705
  if (isAlphaNumeric(char) || char === '-' || char === '_' || char === ':') {
@@ -2376,33 +2390,14 @@ function parseTriggerCommand(ctx, identifierNode) {
2376
2390
  });
2377
2391
  }
2378
2392
  }
2379
- while (!isCommandBoundary(ctx)) {
2380
- allArgs.push(ctx.parsePrimary());
2381
- }
2382
- let operationIndex = -1;
2383
- let operationKeyword = 'on';
2384
- for (let i = 0; i < allArgs.length; i++) {
2385
- const arg = allArgs[i];
2386
- const argRecord = arg;
2387
- const argValue = argRecord.name || argRecord.value;
2388
- if ((arg.type === 'identifier' || arg.type === 'literal' || arg.type === 'keyword') &&
2389
- (argValue === 'on' || argValue === 'to')) {
2390
- operationIndex = i;
2391
- operationKeyword = argValue;
2392
- 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());
2393
2399
  }
2394
2400
  }
2395
- const finalArgs = [];
2396
- if (operationIndex === -1) {
2397
- finalArgs.push(...allArgs);
2398
- }
2399
- else {
2400
- const eventArgs = allArgs.slice(0, operationIndex);
2401
- const targetArgs = allArgs.slice(operationIndex + 1);
2402
- finalArgs.push(...eventArgs);
2403
- finalArgs.push(ctx.createIdentifier(operationKeyword));
2404
- finalArgs.push(...targetArgs);
2405
- }
2406
2401
  return CommandNodeBuilder.fromIdentifier(identifierNode)
2407
2402
  .withArgs(...finalArgs)
2408
2403
  .endingAt(ctx.getPosition())
@@ -3122,8 +3117,13 @@ function parseSwapCommand(ctx, identifierNode) {
3122
3117
 
3123
3118
  function parseWaitCommand(ctx, commandToken) {
3124
3119
  const args = [];
3125
- if (ctx.checkTimeExpression() || ctx.checkLiteral()) {
3126
- 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();
3127
3127
  args.push(timeExpr);
3128
3128
  return CommandNodeBuilder.from(commandToken)
3129
3129
  .withArgs(...args)
@@ -3309,43 +3309,24 @@ function parsePropertyOfTarget(ctx, startPosition) {
3309
3309
  return null;
3310
3310
  const thePosition = ctx.savePosition();
3311
3311
  ctx.advance();
3312
- const nextToken = ctx.peek();
3313
- const tokenAfterNext = ctx.peekAt(1);
3314
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.OF) {
3315
- const propertyToken = ctx.advance();
3316
- if (ctx.check(KEYWORDS.OF)) {
3317
- ctx.advance();
3318
- const targetToken = ctx.advance();
3319
- const isIdSelector = targetToken.value.startsWith('#');
3320
- return {
3321
- type: 'propertyOfExpression',
3322
- property: {
3323
- type: 'identifier',
3324
- name: propertyToken.value,
3325
- start: propertyToken.start,
3326
- end: propertyToken.end,
3327
- },
3328
- target: {
3329
- type: isIdSelector ? 'idSelector' : 'cssSelector',
3330
- value: targetToken.value,
3331
- start: targetToken.start,
3332
- end: targetToken.end,
3333
- },
3334
- start: startPosition,
3335
- end: ctx.savePosition(),
3336
- };
3337
- }
3338
- ctx.restorePosition(startPosition);
3312
+ const propertyExpr = ctx.parseExpression();
3313
+ if (!propertyExpr) {
3314
+ ctx.restorePosition(thePosition);
3339
3315
  return null;
3340
3316
  }
3341
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.TO) {
3342
- const variableToken = ctx.advance();
3343
- return {
3344
- type: 'identifier',
3345
- name: variableToken.value,
3346
- start: variableToken.start,
3347
- end: variableToken.end,
3348
- };
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;
3349
3330
  }
3350
3331
  ctx.restorePosition(thePosition);
3351
3332
  return null;
@@ -3704,6 +3685,71 @@ function parseFetchNakedNamedArgs(ctx) {
3704
3685
  column: startPos.column,
3705
3686
  };
3706
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
+ }
3707
3753
  function parseJsCommand(ctx, identifierNode) {
3708
3754
  const parameters = [];
3709
3755
  if (ctx.match('(')) {
@@ -3716,11 +3762,12 @@ function parseJsCommand(ctx, identifierNode) {
3716
3762
  ctx.consume(')', 'Expected ) after js parameters');
3717
3763
  }
3718
3764
  const jsCodeStart = ctx.peek().start;
3719
- 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;
3720
3769
  ctx.advance();
3721
3770
  }
3722
- const endToken = ctx.peek();
3723
- const jsCodeEnd = endToken.start;
3724
3771
  ctx.consume(KEYWORDS.END, 'Expected end after js code body');
3725
3772
  const rawSlice = ctx.getInputSlice(jsCodeStart, jsCodeEnd);
3726
3773
  const code = rawSlice.trim();
@@ -4623,7 +4670,12 @@ class Parser {
4623
4670
  }
4624
4671
  else {
4625
4672
  const commandName = expr.name.toLowerCase();
4626
- if (commandName === 'wait' && this.checkTimeExpression()) {
4673
+ if (commandName === 'wait' &&
4674
+ (this.checkTimeExpression() ||
4675
+ this.checkNumber() ||
4676
+ this.checkIdentifier() ||
4677
+ this.checkContextVar() ||
4678
+ this.check('('))) {
4627
4679
  const command = this.createCommandFromIdentifier(expr);
4628
4680
  if (command) {
4629
4681
  expr = command;
@@ -5842,9 +5894,7 @@ class Parser {
5842
5894
  while (!this.isAtEnd() && !this.check('end')) {
5843
5895
  if (this.match('on')) {
5844
5896
  const handlerPos = this.getPosition();
5845
- const eventToken = this.peek();
5846
- const eventName = eventToken.value;
5847
- this.advance();
5897
+ const eventName = this.parseEventNameWithNamespace("Expected event name after 'on'");
5848
5898
  const eventArgs = [];
5849
5899
  if (this.check('(')) {
5850
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.0",
3
+ "version": "2.3.0",
4
4
  "description": "Multilingual, tree-shakeable hyperscript",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -236,7 +236,7 @@
236
236
  "@vitest/coverage-v8": "^4.0.17",
237
237
  "@vitest/ui": "^4.0.17",
238
238
  "better-sqlite3": "^12.6.2",
239
- "esbuild": "0.27.3",
239
+ "esbuild": "0.27.4",
240
240
  "eslint": "^8.57.1",
241
241
  "eslint-config-prettier": "^9.0.0",
242
242
  "eslint-plugin-prettier": "^5.0.0",