All files / src/ast/reference indexer.ts

90.62% Statements 29/32
92% Branches 23/25
85.71% Functions 6/7
90.62% Lines 29/32

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                  26x         6x         6x       15x 15x   15x 11x 11x 6x           5x     4x 1x     3x 1x     2x                       5x 5x   5x 2x 2x 2x 2x                 3x 2x 2x     1x 1x 1x                     6x      
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);
      Eif (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;
    }
 
    Eif (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() + ']';
  }
}