public removeFrom()

in packages/jsii/lib/transforms/deprecated-remover.ts [68:262]


  public removeFrom(assembly: Assembly): readonly JsiiDiagnostic[] {
    if (assembly.types == null) {
      return [];
    }

    const strippedFqns = new Set<string>();
    const replaceWithClass = new Map<string, string>();
    const replaceWithInterfaces = new Map<string, readonly string[]>();

    // Find all types that will be stripped out
    for (const [fqn, typeInfo] of Object.entries(assembly.types)) {
      if (typeInfo.docs?.stability === Stability.Deprecated) {
        if (!this.shouldFqnBeStripped(fqn)) {
          continue;
        }
        strippedFqns.add(fqn);

        if (isClassType(typeInfo) && typeInfo.base != null) {
          replaceWithClass.set(fqn, typeInfo.base);
        }
        if (isClassOrInterfaceType(typeInfo) && typeInfo.interfaces != null) {
          replaceWithInterfaces.set(fqn, typeInfo.interfaces);
        }

        this.nodesToRemove.add(bindings.getRelatedNode(typeInfo)!);
      }
    }

    for (const [fqn, typeInfo] of Object.entries(assembly.types)) {
      // Ignore `@deprecated` types
      if (strippedFqns.has(fqn)) {
        continue;
      }

      // Enums cannot have references to `@deprecated` types, but can have deprecated members
      if (isEnumType(typeInfo)) {
        const enumNode = bindings.getEnumRelatedNode(typeInfo)!;
        const members: EnumMember[] = [];
        typeInfo.members.forEach((mem) => {
          if (
            mem.docs?.stability === Stability.Deprecated &&
            this.shouldFqnBeStripped(`${fqn}#${mem.name}`)
          ) {
            const matchingMemberNode = enumNode.members.find(
              (enumMem) => enumMem.name.getText() === mem.name,
            );
            if (matchingMemberNode) {
              this.nodesToRemove.add(matchingMemberNode);
            }
          } else {
            members.push(mem);
          }
        });
        typeInfo.members = members;
        continue;
      }

      // For classes, we erase `@deprecated` base classes, replacing as needed
      const additionalInterfaces = new Set<string>();
      if (
        isClassType(typeInfo) &&
        typeInfo.base != null &&
        strippedFqns.has(typeInfo.base)
      ) {
        while (typeInfo.base != null && strippedFqns.has(typeInfo.base)) {
          const oldBase = assembly.types[typeInfo.base] as ClassType;
          oldBase.interfaces?.forEach((fqn) => additionalInterfaces.add(fqn));
          typeInfo.base = replaceWithClass.get(typeInfo.base);
        }
        this.transformations.push(
          typeInfo.base != null
            ? Transformation.replaceBaseClass(
                this.typeChecker,
                bindings.getClassRelatedNode(typeInfo)!,
                typeInfo.base in assembly.types
                  ? bindings.getClassRelatedNode(
                      assembly.types[typeInfo.base] as ClassType,
                    ) ?? typeInfo.base
                  : typeInfo.base,
              )
            : Transformation.removeBaseClass(
                this.typeChecker,
                bindings.getClassRelatedNode(typeInfo)!,
              ),
        );
      }

      // Be defensive in case we add other kinds in the future
      if (!isClassOrInterfaceType(typeInfo)) {
        throw new Error(
          `Unhandled type encountered! ${JSON.stringify(typeInfo, null, 2)}`,
        );
      }

      // Strip all `@deprecated` interfaces from the inheritance tree, replacing as needed
      if (
        typeInfo.interfaces?.some((fqn) => strippedFqns.has(fqn)) ||
        additionalInterfaces.size > 0
      ) {
        const originalSet = new Set(typeInfo.interfaces ?? []);
        const newSet = new Set<string>();

        const candidates = Array.from(
          new Set([...originalSet, ...additionalInterfaces]),
        );
        while (candidates.length > 0) {
          const fqn = candidates.pop()!;
          if (!strippedFqns.has(fqn)) {
            newSet.add(fqn);
            if (!originalSet.has(fqn)) {
              this.transformations.push(
                Transformation.addInterface(
                  this.typeChecker,
                  bindings.getClassOrInterfaceRelatedNode(typeInfo)!,
                  fqn in assembly.types
                    ? bindings.getInterfaceRelatedNode(
                        assembly.types[fqn] as InterfaceType,
                      ) ?? fqn
                    : fqn,
                ),
              );
            }
            continue;
          }
          if (originalSet.has(fqn)) {
            this.transformations.push(
              Transformation.removeInterface(
                this.typeChecker,
                bindings.getClassOrInterfaceRelatedNode(typeInfo)!,
                bindings.getInterfaceRelatedNode(
                  assembly.types[fqn] as InterfaceType,
                )!,
              ),
            );
          }
          const replacement = replaceWithInterfaces.get(fqn);
          if (replacement != null) {
            candidates.push(...replacement);
          }
        }

        typeInfo.interfaces =
          newSet.size > 0 ? Array.from(newSet).sort() : undefined;
      }

      // Drop all `@deprecated` members, and remove "overrides" from stripped types
      const methods: Method[] = [];
      const properties: Property[] = [];
      typeInfo.methods?.forEach((meth) => {
        if (
          meth.docs?.stability === Stability.Deprecated &&
          this.shouldFqnBeStripped(`${fqn}#${meth.name}`)
        ) {
          this.nodesToRemove.add(bindings.getMethodRelatedNode(meth)!);
        } else {
          methods.push(
            meth.overrides != null && strippedFqns.has(meth.overrides)
              ? { ...meth, overrides: undefined }
              : meth,
          );
        }
      });
      typeInfo.methods = typeInfo.methods ? methods : undefined;
      typeInfo.properties?.forEach((prop) => {
        if (
          prop.docs?.stability === Stability.Deprecated &&
          this.shouldFqnBeStripped(`${fqn}#${prop.name}`)
        ) {
          this.nodesToRemove.add(bindings.getParameterRelatedNode(prop)!);
        } else {
          properties.push(
            prop.overrides != null && strippedFqns.has(prop.overrides)
              ? { ...prop, overrides: undefined }
              : prop,
          );
        }
      });
      typeInfo.properties = typeInfo.properties ? properties : undefined;
    }

    const diagnostics = this.findLeftoverUseOfDeprecatedAPIs(
      assembly,
      strippedFqns,
    );

    // Remove all `@deprecated` types, after we did everything, so we could
    // still access the related nodes from the assembly object.
    for (const fqn of strippedFqns) {
      if (this.shouldFqnBeStripped(fqn)) {
        delete assembly.types[fqn];
      }
    }

    return diagnostics;
  }