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;
}