public removeFrom()

in src/transforms/deprecated-remover.ts [69:228]


  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[] = [];
        for (const mem of typeInfo.members) {
          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;
          if (oldBase.interfaces) for (const addFqn of oldBase.interfaces) additionalInterfaces.add(addFqn);
          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((addFqn) => strippedFqns.has(addFqn)) || 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 candidateFqn = candidates.pop()!;
          if (!strippedFqns.has(candidateFqn)) {
            newSet.add(candidateFqn);
            if (!originalSet.has(candidateFqn)) {
              this.transformations.push(
                Transformation.addInterface(
                  this.typeChecker,
                  bindings.getClassOrInterfaceRelatedNode(typeInfo)!,
                  candidateFqn in assembly.types
                    ? bindings.getInterfaceRelatedNode(assembly.types[candidateFqn] as InterfaceType) ?? candidateFqn
                    : candidateFqn,
                ),
              );
            }
            continue;
          }
          if (originalSet.has(candidateFqn)) {
            this.transformations.push(
              Transformation.removeInterface(
                this.typeChecker,
                bindings.getClassOrInterfaceRelatedNode(typeInfo)!,
                bindings.getInterfaceRelatedNode(assembly.types[candidateFqn] as InterfaceType)!,
              ),
            );
          }
          const replacement = replaceWithInterfaces.get(candidateFqn);
          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[] = [];
      if (typeInfo.methods) {
        for (const meth of typeInfo.methods) {
          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;
      if (typeInfo.properties) {
        for (const prop of typeInfo.properties) {
          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;
  }