import { ErrorNames, createMappedError } from './errors'; export interface IBindingContext { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: PropertyKey]: any; } export interface IOverrideContext { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: PropertyKey]: any; } export class Scope { private constructor( public parent: Scope | null, public bindingContext: IBindingContext, public overrideContext: IOverrideContext, public readonly isBoundary: boolean, ) { } public static getContext(scope: Scope, name: string, ancestor: number): IBindingContext | IOverrideContext | undefined | null { if (scope == null) { throw createMappedError(ErrorNames.null_scope); } let overrideContext: IOverrideContext | null = scope.overrideContext; let currentScope: Scope | null = scope; if (ancestor > 0) { // jump up the required number of ancestor contexts (eg $parent.$parent requires two jumps) while (ancestor > 0) { ancestor--; currentScope = currentScope.parent; if (currentScope == null) { return void 0; } } overrideContext = currentScope.overrideContext; // Here we are giving benefit of doubt considering the dev has used one or more `$parent` token, and thus should know what s/he is targeting. return name in overrideContext ? overrideContext : currentScope.bindingContext; } // walk the scope hierarchy until // the first scope that has the property in its contexts // or // the closet boundary scope // ------------------------- // this behavior is different with v1 // where it would fallback to the immediate scope instead of the root one // TODO: maybe avoid immediate loop and return earlier // ------------------------- while ( currentScope != null && !currentScope.isBoundary && !(name in currentScope.overrideContext) && !(name in currentScope.bindingContext) ) { currentScope = currentScope.parent; } if (currentScope == null) { return scope.bindingContext; } overrideContext = currentScope.overrideContext; return name in overrideContext ? overrideContext : currentScope.bindingContext; } /** * Create a new `Scope` backed by the provided `BindingContext` and a new standalone `OverrideContext`. * * Use this overload when the scope is for the root component, in a unit test, * or when you simply want to prevent binding expressions from traversing up the scope. * * @param bc - The `BindingContext` to back the `Scope` with. */ public static create(bc: object): Scope; /** * Create a new `Scope` backed by the provided `BindingContext` and `OverrideContext`. * * @param bc - The `BindingContext` to back the `Scope` with. * @param oc - The `OverrideContext` to back the `Scope` with. * If a binding expression attempts to access a property that does not exist on the `BindingContext` * during binding, it will traverse up via the `parentScope` of the scope until * it finds the property. */ public static create(bc: object, oc: IOverrideContext, isBoundary?: boolean): Scope; /** * Create a new `Scope` backed by the provided `BindingContext` and `OverrideContext`. * * Use this overload when the scope is for the root component, in a unit test, * or when you simply want to prevent binding expressions from traversing up the scope. * * @param bc - The `BindingContext` to back the `Scope` with. * @param oc - null. This overload is functionally equivalent to not passing this argument at all. */ public static create(bc: object, oc: null, isBoundary?: boolean): Scope; public static create(bc: object, oc?: IOverrideContext | null, isBoundary?: boolean): Scope { if (bc == null) { throw createMappedError(ErrorNames.create_scope_with_null_context); } return new Scope(null, bc as IBindingContext, oc ?? new OverrideContext(), isBoundary ?? false); } public static fromParent(ps: Scope | null, bc: object, oc: IOverrideContext = new OverrideContext()): Scope { if (ps == null) { throw createMappedError(ErrorNames.null_scope); } return new Scope(ps, bc as IBindingContext, oc, false); } } /** * A class for creating context in synthetic scope to keep the number of classes of context in scope small */ export class BindingContext implements IBindingContext { [key: PropertyKey]: unknown; public constructor(); public constructor(key: PropertyKey, value: unknown); public constructor(key?: PropertyKey, value?: unknown) { if (key !== void 0) { this[key] = value; } } } class OverrideContext implements IOverrideContext { [key: PropertyKey]: unknown; }