All files / src/evaluation-context type-descriptor-accessor.ts

88.88% Statements 16/18
95% Branches 19/20
80% Functions 4/5
88.23% Lines 15/17

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                                      206x   204x                   15x     15x           15x 9x       6x 6x 1x   5x 3x         2x               6x                 1x 1x      
import type { EvaluationContext } from './evaluation-context.js';
import type { PropertyAccessor } from './property-accessor.js';
import { TypedValue } from '../typed-value.js';
import type { TypeDescriptor } from '../type/type-descriptor.js';
import { SpelEvaluationException } from '../error/spel-evaluation-exception.js';
import { SpelMessage } from '../error/spel-message.js';
 
/**
 * TypeDescriptor property accessor
 *
 * Handles T(Type).staticMethod and T(Type).staticField access,
 * Extends the PropertyAccessor chain to TypeDescriptor objects.
 */
export class TypeDescriptorAccessor implements PropertyAccessor {
  public getSpecificTargetClasses(): null {
    return null;
  }
 
  public canRead(_context: EvaluationContext, target: unknown, _name: string): boolean {
    if (target === null || target === undefined) return false;
    // TypeDescriptor objects have name, constructor, staticMethods, staticFields
    return (
      typeof target === 'object' &&
      'name' in target &&
      'constructor' in target &&
      'staticMethods' in target &&
      'staticFields' in target
    );
  }
 
  public read(_context: EvaluationContext, target: unknown, name: string): TypedValue {
    const td = target as TypeDescriptor;
 
    // Static method: call it
    Iif (typeof td.staticMethods[name] === 'function') {
      // Return the method itself — caller will invoke with args
      return new TypedValue(td.staticMethods[name]);
    }
 
    // Static field: return value
    if (name in td.staticFields) {
      return new TypedValue(td.staticFields[name]);
    }
 
    // Check constructor-level static property
    const ctor = td.constructor as unknown as Record<string, unknown>;
    if (typeof ctor[name] === 'function') {
      return new TypedValue(ctor[name]);
    }
    if (name in ctor) {
      return new TypedValue(ctor[name]);
    }
 
    // Spring reports an unknown member of a type. Returning null here would mask
    // a misspelled field name as a legitimate null value.
    throw new SpelEvaluationException(
      -1,
      SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE,
      `${td.name}.${name}`,
    );
  }
 
  public canWrite(_context: EvaluationContext, target: unknown, _name: string): boolean {
    return typeof target === 'object' && target !== null && 'staticFields' in target;
  }
 
  public write(
    _context: EvaluationContext,
    target: unknown,
    name: string,
    newValue: unknown,
  ): void {
    const td = target as TypeDescriptor;
    td.staticFields[name] = newValue;
  }
}