All files / src/evaluation-context reflective-method-resolver.ts

100% Statements 16/16
100% Branches 14/14
100% Functions 1/1
100% Lines 16/16

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                                154x 4x               150x 76x               74x 25x               49x 31x       18x 18x 18x 11x 11x 11x   2x                   7x      
import type { EvaluationContext } from './evaluation-context.js';
import { TypedValue } from '../typed-value.js';
import type { MethodResolver } from './method-resolver.js';
import { SpelEvaluationException } from '../error/spel-evaluation-exception.js';
import { SpelMessage } from '../error/spel-message.js';
import { invokeStringMethod } from './java-string-methods.js';
import { isTypeDescriptor } from '../type/type-descriptor.js';
import { invokeNumberMethod } from './java-number-methods.js';
 
export class ReflectiveMethodResolver implements MethodResolver {
  public resolve(
    _context: EvaluationContext,
    target: unknown,
    name: string,
    args: unknown[],
  ): TypedValue | null {
    if (target === null || target === undefined) {
      return null;
    }
 
    // A string is resolved against java.lang.String only. JavaScript's string
    // methods are deliberately not consulted, because several share a name with
    // a Java method but not its semantics: `replaceAll` takes a regular
    // expression in Java and a literal in JavaScript, so the JavaScript
    // implementation silently returned the input unchanged.
    if (typeof target === 'string') {
      return invokeStringMethod(target, name, args);
    }
 
    // A resolved type handle resolves only against its own static members. Its
    // JavaScript prototype members must not win the lookup: `valueOf` exists on
    // Object.prototype, so `T(String).valueOf(42)` would otherwise return the
    // handle itself instead of calling String.valueOf. This is the same shadowing
    // problem the string branch above addresses, in a third place.
    if (isTypeDescriptor(target)) {
      return new TypedValue(target.callStaticMethod(name, ...args));
    }
 
    // A number is resolved against the Java wrapper classes only, for the same
    // reason a string is resolved against java.lang.String only: JavaScript's
    // Number offers a different set under different names, and `toFixed` and
    // `toExponential` were callable purely because JavaScript provides them while
    // no Java type does.
    if (typeof target === 'number' || typeof target === 'bigint') {
      return invokeNumberMethod(target, name, args);
    }
 
    // Objects, arrays and maps fall back to their own methods.
    const targetObj = target as Record<string, unknown>;
    const fn = targetObj[name];
    if (typeof fn === 'function') {
      try {
        const result = (fn as (...args: unknown[]) => unknown).apply(target, args);
        return new TypedValue(result);
      } catch (e) {
        throw new SpelEvaluationException(
          -1,
          SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION,
          name,
          (e as Error).message,
        );
      }
    }
 
    // Not found — return null so the accessor chain or another resolver can try.
    return null;
  }
}