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