private calculateSlotNames()

in packages/sqrl/src/compile/SqrlCompiledOutput.ts [121:213]


  private calculateSlotNames() {
    const used: Set<Slot> = new Set();
    const current: Set<Slot> = new Set();

    const throwLoopError = (slot: Slot, slotNames: string[]) => {
      // @TODO: This invariant protects us from allowing loops inside sqrl,
      // but it's implemented at too late a stage to provide nice error
      // messages. Ideally we'd have it higher up as well.

      slotNames = slotNames.filter(isValidFeatureName).sort();
      let errorMessage: string;
      if (isValidFeatureName(slot.name)) {
        errorMessage = `Feature '${slot.name}' depends on itself`;
        slotNames = slotNames.filter((name) => name !== slot.name);
        if (slotNames.length) {
          errorMessage += ", see " + slotNames.join(", ");
        }
      } else {
        this.warn(
          {},
          "Feature loop detected on non-feature slot:: %s",
          slot.name
        );
        errorMessage =
          "Feature loop detected with features: " + slotNames.join(", ");
      }

      // @NOTE: You get much better error messages with this, but it's not user safe
      // const loops = this.printFeatureLoop(slot);
      // errorMessage += '\nDetected loops:\n  ' + loops.join('\n  ');
      throw buildSqrlError(slot.finalizedAst(), errorMessage);
    };

    const recurseUsedSlot = (slot: Slot) => {
      if (used.has(slot)) {
        return;
      } else {
        if (current.has(slot)) {
          throwLoopError(
            slot,
            Array.from(current).map((slot) => slot.name)
          );
        }
      }

      current.add(slot);

      let slotExpr: Expr;
      try {
        slotExpr = this.exprForSlot(slot.name);
      } catch (err) {
        if (err instanceof ExprLoopError) {
          const names = Object.keys(this.slotExprMap).filter((name) => {
            return this.slotExprMap[name] === null;
          });
          throwLoopError(slot, names);
        } else {
          throw err;
        }
      }
      walkExpr(slotExpr, (expr) => {
        (expr.load || []).forEach((slot) => {
          if (!used.has(slot)) {
            recurseUsedSlot(slot);
          }
        });
      });
      current.delete(slot);
      used.add(slot);
    };

    let slotNames: string[];
    if (this.slotFilter) {
      Object.entries(this.slots).forEach(([name, slot]) => {
        if (this.slotFilter(name)) {
          recurseUsedSlot(slot);
        }
      });

      slotNames = Object.keys(this.slots).filter((name) => {
        return used.has(this.slots[name]);
      });
    } else {
      slotNames = Object.keys(this.slots);
      // This recurse is required for picking up cycles, might be able to remove one day
      Object.values(this.slots).forEach((slot) => recurseUsedSlot(slot));
    }

    this._slotNames = slotNames;
    this._usedSlotNames = slotNames.map((name) =>
      this.slots.hasOwnProperty(name) ? name : null
    );
  }