UNPKG

typir

Version:

General purpose type checking library

215 lines 11.1 kB
/****************************************************************************** * Copyright 2024 TypeFox GmbH * This program and the accompanying materials are made available under the * terms of the MIT License, which is available in the project root. ******************************************************************************/ import { removeFromArray, toArray } from '../utils/utils.js'; /** * The purpose of this class is to inform its listeners, when all given TypeReferences reached their specified initialization state (or a later state). * After that, the listeners might be informed multiple times, * if at least one of the TypeReferences was unresolved/invalid, but later on all TypeReferences are again in the desired state, and so on. */ export class WaitingForIdentifiableAndCompletedTypeReferences { constructor(waitForRefsToBeIdentified, waitForRefsToBeCompleted) { /** Remembers whether all TypeReferences are in the desired states (true) or not (false). */ this.fulfilled = false; /** This is required for cyclic type definitions: * In case of two types A, B which use each other for their properties (e.g. class A {p: B} and class B {p: A}), the case easily occurs, * that the types A and B (respectively their WaitingFor... instances) are waiting for each other and therefore waiting for each other. * In order to solve these cycles, types which are part of such "dependency cycles" should be ignored during waiting, * e.g. A should not waiting B and B should not wait for A. * These types to ignore are stored in the following Set. */ this.typesToIgnoreForCycles = new Set(); /** These listeners will be informed once, when all TypeReferences are in the desired state. * If some of these TypeReferences are invalid (the listeners will not be informed about this) and later in the desired state again, * the listeners will be informed again, and so on. */ this.listeners = []; // remember the relevant TypeReferences to wait for this.waitForRefsIdentified = waitForRefsToBeIdentified; this.waitForRefsCompleted = waitForRefsToBeCompleted; // register to get updates for the relevant TypeReferences toArray(this.waitForRefsIdentified).forEach(ref => ref.addListener(this, true)); // 'true' calls 'checkIfFulfilled()' to check, whether everything might already be fulfilled toArray(this.waitForRefsCompleted).forEach(ref => ref.addListener(this, true)); } deconstruct() { var _a, _b; this.listeners.splice(0, this.listeners.length); (_a = this.waitForRefsIdentified) === null || _a === void 0 ? void 0 : _a.forEach(ref => ref.removeListener(this)); (_b = this.waitForRefsCompleted) === null || _b === void 0 ? void 0 : _b.forEach(ref => ref.removeListener(this)); this.typesToIgnoreForCycles.clear(); } addListener(newListener, informAboutCurrentState) { this.listeners.push(newListener); // inform the new listener if (informAboutCurrentState) { if (this.fulfilled) { newListener.onFulfilled(this); } else { newListener.onInvalidated(this); } } } removeListener(listenerToRemove) { removeFromArray(listenerToRemove, this.listeners); } /** * This method is called to inform about additional types which can be ignored during the waiting/resolving process. * This helps to deal with cycles in type dependencies. * @param moreTypesToIgnore might contain duplicates, which are filtered internally */ addTypesToIgnoreForCycles(moreTypesToIgnore) { var _a, _b; // identify the actual new types to ignore (filtering out the types which are already ignored) const newTypesToIgnore = new Set(); for (const typeToIgnore of moreTypesToIgnore) { if (this.typesToIgnoreForCycles.has(typeToIgnore)) { // ignore this additional type, required to break the propagation, since the propagation itself becomes cyclic as well in case of cyclic types! } else { newTypesToIgnore.add(typeToIgnore); this.typesToIgnoreForCycles.add(typeToIgnore); } } if (newTypesToIgnore.size <= 0) { // no new types to ignore => do nothing } else { // propagate the new types to ignore recursively to all direct and indirect referenced types ... // ... which should be identifiable (or completed) for (const ref of ((_a = this.waitForRefsIdentified) !== null && _a !== void 0 ? _a : [])) { const refType = ref.getType(); if (refType === null || refType === void 0 ? void 0 : refType.isInStateOrLater('Identifiable')) { // this reference is already ready } else { refType === null || refType === void 0 ? void 0 : refType.ignoreDependingTypesDuringInitialization(newTypesToIgnore); } } // ... which should be completed for (const ref of ((_b = this.waitForRefsCompleted) !== null && _b !== void 0 ? _b : [])) { const refType = ref.getType(); if (refType === null || refType === void 0 ? void 0 : refType.isInStateOrLater('Completed')) { // this reference is already ready } else { refType === null || refType === void 0 ? void 0 : refType.ignoreDependingTypesDuringInitialization(newTypesToIgnore); } } // since there are more types to ignore, check again this.checkIfFulfilled(); } } onTypeReferenceResolved(_reference, resolvedType) { // inform the referenced type about the types to ignore for completion, so that the type could switch to its next phase (if needed) resolvedType.ignoreDependingTypesDuringInitialization(this.typesToIgnoreForCycles); resolvedType.addListener(this, false); // check, whether all TypeReferences are resolved and the resolved types are in the expected state this.checkIfFulfilled(); // TODO is a more performant solution possible, e.g. by counting or using "resolvedType"? } onTypeReferenceInvalidated(_reference, previousType) { // since at least one TypeReference was reset, the listeners might be informed (again), when all TypeReferences reached the desired state (again) this.switchToNotFulfilled(); if (previousType) { previousType.removeListener(this); } } onSwitchedToIdentifiable(_type) { // check, whether all TypeReferences are resolved and the resolved types are in the expected state this.checkIfFulfilled(); // TODO is a more performant solution possible, e.g. by counting or using "resolvedType"? } onSwitchedToCompleted(_type) { // check, whether all TypeReferences are resolved and the resolved types are in the expected state this.checkIfFulfilled(); // TODO is a more performant solution possible, e.g. by counting or using "resolvedType"? } onSwitchedToInvalid(_type) { // since at least one TypeReference was reset, the listeners might be informed (again), when all TypeReferences reached the desired state (again) this.switchToNotFulfilled(); } checkIfFulfilled() { // already informed => do not inform again if (this.fulfilled) { return; } for (const ref of toArray(this.waitForRefsIdentified)) { const refType = ref.getType(); if (refType && (refType.isInStateOrLater('Identifiable') || this.typesToIgnoreForCycles.has(refType))) { // that is fine } else { return; } } for (const ref of toArray(this.waitForRefsCompleted)) { const refType = ref.getType(); if (refType && (refType.isInStateOrLater('Completed') || this.typesToIgnoreForCycles.has(refType))) { // that is fine } else { return; } } // everything is fine now! => inform all listeners this.fulfilled = true; // don't inform the listeners again this.listeners.slice().forEach(listener => listener.onFulfilled(this)); // slice() prevents issues with removal of listeners during notifications this.typesToIgnoreForCycles.clear(); // otherwise deleted types remain in this Set forever } switchToNotFulfilled() { // since at least one TypeReference was reset, the listeners might be informed (again), when all TypeReferences reached the desired state (again) if (this.fulfilled) { this.fulfilled = false; this.listeners.slice().forEach(listener => listener.onInvalidated(this)); // slice() prevents issues with removal of listeners during notifications } else { // already not fulfilled => nothing to do now } } isFulfilled() { return this.fulfilled; } } export class WaitingForInvalidTypeReferences { constructor(waitForRefsToBeInvalid) { /** These listeners will be informed, when all TypeReferences are in the desired state. */ this.listeners = []; // remember the relevant TypeReferences this.waitForRefsInvalid = waitForRefsToBeInvalid; this.counterInvalid = this.waitForRefsInvalid.filter(ref => ref.getType() === undefined || ref.getType().isInState('Invalid')).length; // register to get updates for the relevant TypeReferences this.waitForRefsInvalid.forEach(ref => ref.addListener(this, false)); } deconstruct() { this.listeners.splice(0, this.listeners.length); this.waitForRefsInvalid.forEach(ref => ref.removeListener(this)); } addListener(newListener, informIfAlreadyFulfilled) { this.listeners.push(newListener); // inform new listener, if the state is already reached! if (informIfAlreadyFulfilled && this.isFulfilled()) { newListener(this); } } removeListener(listenerToRemove) { removeFromArray(listenerToRemove, this.listeners); } onTypeReferenceResolved(_reference, _resolvedType) { this.counterInvalid--; } onTypeReferenceInvalidated(_reference, _previousType) { this.counterInvalid++; if (this.isFulfilled()) { this.listeners.slice().forEach(listener => listener(this)); } } isFulfilled() { return this.counterInvalid === this.waitForRefsInvalid.length && this.waitForRefsInvalid.length >= 1; } getWaitForRefsInvalid() { return this.waitForRefsInvalid; } } //# sourceMappingURL=type-waiting.js.map