All files / src standard-evaluation-context.ts

97.29% Statements 36/37
100% Branches 4/4
90% Functions 18/20
97.29% Lines 36/37

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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121                                5785x 5785x 5785x 5785x             5785x   5785x 5785x 5785x 5785x   5785x           5785x       5785x   1x               5785x       6011x     4x       68x     61x       18x     3x       213x     2x       103x     1x       71x     22x       13x     12x       3x       249x 249x 249x 249x 249x 1x   249x      
import { TypedValue } from './typed-value.js';
import type { EvaluationContext } from './evaluation-context/evaluation-context.js';
import type { PropertyAccessor } from './evaluation-context/property-accessor.js';
import type { MethodResolver } from './evaluation-context/method-resolver.js';
import type { TypeLocator } from './type/type-locator.js';
import type { BeanResolver } from './bean/bean-resolver.js';
import { SpelTypeConverter } from './bridge/type-coercion.js';
import { MapAccessor } from './evaluation-context/map-accessor.js';
import { ArrayAccessor } from './evaluation-context/array-accessor.js';
import { ReflectivePropertyAccessor } from './evaluation-context/reflective-property-accessor.js';
import { TypeDescriptorAccessor } from './evaluation-context/type-descriptor-accessor.js';
import { ReflectiveMethodResolver } from './evaluation-context/reflective-method-resolver.js';
import { StandardTypeLocator } from './type/standard-type-locator.js';
 
export class StandardEvaluationContext implements EvaluationContext {
  private rootObject: TypedValue;
  private readonly variables = new Map<string, TypedValue>();
  private readonly functions = new Map<string, (...args: unknown[]) => unknown>();
  private readonly propertyAccessors: PropertyAccessor[] = [];
  private readonly methodResolvers: MethodResolver[] = [];
 
  private typeLocator: TypeLocator;
  private beanResolver: BeanResolver;
  private typeConverter: SpelTypeConverter;
 
  constructor(rootObject?: unknown) {
    this.rootObject = new TypedValue(rootObject);
 
    this.propertyAccessors.push(new MapAccessor());
    this.propertyAccessors.push(new ArrayAccessor());
    this.propertyAccessors.push(new TypeDescriptorAccessor());
    this.propertyAccessors.push(new ReflectivePropertyAccessor());
 
    this.methodResolvers.push(new ReflectiveMethodResolver());
 
    // A working type locator is installed by default, matching Spring's
    // StandardEvaluationContext, so `T(String)` and `instanceof` work out of the
    // box. This previously held a stub that always threw, which meant the
    // `T(...)` and `instanceof` syntax documented in the README could not be used.
    this.typeLocator = new StandardTypeLocator();
 
    // Left unset on purpose: Spring's StandardEvaluationContext also has no bean
    // resolver, so `@bean` fails until one is installed via setBeanResolver().
    this.beanResolver = {
      resolve: (name: string) => {
        throw new Error(`Bean "${name}" not found. BeanResolver not configured.`);
      },
      // Stub: no-op until a real BeanResolver is installed via setBeanResolver()
      // eslint-disable-next-line @typescript-eslint/no-empty-function
      register: () => {},
      has: () => false,
    };
 
    this.typeConverter = new SpelTypeConverter();
  }
 
  public getRootObject(): TypedValue {
    return this.rootObject;
  }
  public setRootObject(obj: unknown): void {
    this.rootObject = new TypedValue(obj);
  }
 
  public lookupVariable(name: string): TypedValue | null {
    return this.variables.get(name) ?? null;
  }
  public setVariable(name: string, value: unknown): void {
    this.variables.set(name, new TypedValue(value));
  }
 
  public lookupFunction(name: string): ((...args: unknown[]) => unknown) | null {
    return this.functions.get(name) ?? null;
  }
  public registerFunction(name: string, fn: (...args: unknown[]) => unknown): void {
    this.functions.set(name, fn);
  }
 
  public getPropertyAccessors(): PropertyAccessor[] {
    return this.propertyAccessors;
  }
  public addPropertyAccessor(accessor: PropertyAccessor): void {
    this.propertyAccessors.push(accessor);
  }
 
  public getMethodResolvers(): MethodResolver[] {
    return this.methodResolvers;
  }
  public addMethodResolver(resolver: MethodResolver): void {
    this.methodResolvers.push(resolver);
  }
 
  public getTypeLocator(): TypeLocator {
    return this.typeLocator;
  }
  public setTypeLocator(typeLocator: TypeLocator): void {
    this.typeLocator = typeLocator;
  }
 
  public getBeanResolver(): BeanResolver {
    return this.beanResolver;
  }
  public setBeanResolver(beanResolver: BeanResolver): void {
    this.beanResolver = beanResolver;
  }
 
  public getTypeConverter(): SpelTypeConverter {
    return this.typeConverter;
  }
 
  public createChildContext(rootObject: unknown): EvaluationContext {
    const child = new StandardEvaluationContext(rootObject);
    child.typeLocator = this.typeLocator;
    child.beanResolver = this.beanResolver;
    child.typeConverter = this.typeConverter;
    for (const [name, fn] of this.functions) {
      child.functions.set(name, fn);
    }
    return child;
  }
}