All files / src/ast/reference method-reference.ts

83.33% Statements 55/66
88.23% Branches 15/17
80% Functions 4/5
83.33% Lines 55/66

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  1x 1x 1x 1x 1x   1x 51x   51x 51x 51x 51x   51x       51x 33x 33x   33x 3x 3x 3x 3x 3x 3x 3x   30x 30x 30x 30x 21x 21x 30x     29x 33x 3x 3x 3x 3x 3x               3x     33x     6x 6x 6x 6x   6x   6x 6x 6x 6x 6x 6x 33x   51x 7x 7x 51x  
import type { ExpressionState } from '../../expression-state.js';
import { TypedValue } from '../../typed-value.js';
import { SpelNodeImpl } from '../spel-node.js';
import { NodeType } from '../../language/node-type.js';
import { SpelEvaluationException } from '../../error/spel-evaluation-exception.js';
import { SpelMessage } from '../../error/spel-message.js';
 
export class MethodReference extends SpelNodeImpl {
  private readonly methodName: string;
 
  constructor(startPos: number, endPos: number, methodName: string, ...args: SpelNodeImpl[]) {
    super(NodeType.METHOD_REFERENCE, startPos, endPos, ...args);
    this.methodName = methodName;
  }
 
  public getMethodName(): string {
    return this.methodName;
  }
 
  public getValueInternal(state: ExpressionState): TypedValue {
    const target = state.getThis().getValue();
    const argValues = this.children.map((c) => c.getValue(state).getValue());
 
    if (target === null || target === undefined) {
      throw new SpelEvaluationException(
        this.startPos,
        SpelMessage.METHOD_NOT_FOUND,
        this.methodName,
        'null',
      );
    }
 
    const context = state.getEvaluationContext();
    for (const resolver of context.getMethodResolvers()) {
      const result = resolver.resolve(context, target, this.methodName, argValues);
      if (result !== null) {
        return result;
      }
    }
 
    // Fallback: try property accessor chain (e.g., TypeDescriptor.staticMethods)
    for (const accessor of context.getPropertyAccessors()) {
      if (accessor.canRead(context, target, this.methodName)) {
        const propValue = accessor.read(context, target, this.methodName).getValue();
        if (typeof propValue === 'function') {
          try {
            return new TypedValue((propValue as (...a: unknown[]) => unknown)(...argValues));
          } catch (e) {
            throw new SpelEvaluationException(
              this.startPos,
              SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION,
              this.methodName,
              (e as Error).message,
            );
          }
        }
        break;
      }
    }
 
    // Also try function registry
    try {
      const fn = state.lookupFunction(this.methodName);
      return new TypedValue(fn(...argValues));
    } catch {
      // not found in functions
    }
 
    throw new SpelEvaluationException(
      this.startPos,
      SpelMessage.METHOD_NOT_FOUND,
      this.methodName,
      typeof target,
    );
  }
 
  public toStringAST(): string {
    return `${this.methodName}(${this.children.map((c) => c.toStringAST()).join(', ')})`;
  }
}