All files / src/ast/reference indexer.ts

83.11% Statements 64/77
92.85% Branches 26/28
85.71% Functions 6/7
83.11% Lines 64/77

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96  1x 1x 1x 1x 1x   1x 1x 24x 24x     1x 6x 6x     1x 6x 6x   1x 13x 13x   13x 9x 9x 6x 6x 6x 6x 6x 6x 3x 3x   12x 1x 1x   13x 1x 1x   2x 2x 2x 2x 2x 13x   1x       1x 5x 5x   5x 2x 2x 2x 2x 2x               3x 2x 2x 2x   5x 1x 1x 1x             5x   1x 6x 6x 1x  
import type { ExpressionState } from '../../expression-state.js';
import { TypedValue } from '../../typed-value.js';
import { SpelEvaluationException } from '../../error/spel-evaluation-exception.js';
import { SpelMessage } from '../../error/spel-message.js';
import { SpelNodeImpl } from '../spel-node.js';
import { NodeType } from '../../language/node-type.js';
 
export class Indexer extends SpelNodeImpl {
  constructor(startPos: number, endPos: number, target: SpelNodeImpl, index: SpelNodeImpl) {
    super(NodeType.INDEXER, startPos, endPos, target, index);
  }
 
  /** Get the target node being indexed */
  public getTarget(): SpelNodeImpl {
    return this.children[0]!;
  }
 
  /** Get the index/key node */
  public getIndex(): SpelNodeImpl {
    return this.children[1]!;
  }
 
  public getValueInternal(state: ExpressionState): TypedValue {
    const target = this.children[0]!.getValue(state).getValue();
    const index = this.children[1]!.getValue(state).getValue();
 
    if (Array.isArray(target)) {
      const idx = Number(index);
      if (isNaN(idx) || idx < 0 || idx >= target.length) {
        throw new SpelEvaluationException(
          this.startPos,
          SpelMessage.INDEX_OUT_OF_BOUNDS,
          String(idx),
        );
      }
      return new TypedValue(target[idx]);
    }
 
    if (target instanceof Map) {
      return new TypedValue(target.get(index));
    }
 
    if (typeof target === 'object' && target !== null) {
      return new TypedValue((target as Record<string, unknown>)[String(index)]);
    }
 
    throw new SpelEvaluationException(
      this.startPos,
      SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
      typeof target,
    );
  }
 
  public override isWritable(_state: ExpressionState): boolean {
    return true;
  }
 
  public override setValue(state: ExpressionState, newValue: unknown): void {
    const target = this.children[0]!.getValue(state).getValue();
    const index = this.children[1]!.getValue(state).getValue();
 
    if (Array.isArray(target)) {
      const idx = Number(index);
      if (idx >= 0 && idx < target.length) {
        (target as unknown[])[idx] = newValue;
        return;
      }
      throw new SpelEvaluationException(
        this.startPos,
        SpelMessage.INDEX_OUT_OF_BOUNDS,
        String(idx),
      );
    }
 
    if (target instanceof Map) {
      target.set(index, newValue);
      return;
    }
 
    if (typeof target === 'object' && target !== null) {
      (target as Record<string, unknown>)[String(index)] = newValue;
      return;
    }
 
    throw new SpelEvaluationException(
      this.startPos,
      SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE,
      typeof target,
    );
  }
 
  public toStringAST(): string {
    return this.getTarget().toStringAST() + '[' + this.getIndex().toStringAST() + ']';
  }
}