function extractExportedSymbol()

in repo-scripts/prune-dts/prune-dts.ts [368:448]


function extractExportedSymbol(
  typeChecker: ts.TypeChecker,
  sourceFile: ts.SourceFile,
  typeName: ts.Node
): ts.Symbol | undefined {
  if (!ts.isIdentifier(typeName)) {
    return undefined;
  }

  if (isExported(typeChecker, sourceFile, typeName)) {
    // Don't replace the type reference if the type is already part of the
    // public API.
    return undefined;
  }

  const localSymbolName = typeName.escapedText;
  const allExportedSymbols = typeChecker.getExportsOfModule(
    typeChecker.getSymbolAtLocation(sourceFile)!
  );

  // Examine all exported types and check if they extend or implement the
  // provided local type. If so, we can use the exported type in lieu of the
  // private type.

  // Short circuit if the local types is already part of the public types.
  for (const symbol of allExportedSymbols) {
    if (symbol.name === localSymbolName) {
      return symbol;
    }
  }

  // See if there is an exported symbol that extends this private symbol.
  // In this case, we can safely use the public symbol instead.
  for (const symbol of allExportedSymbols) {
    for (const declaration of symbol.declarations) {
      if (
        ts.isClassDeclaration(declaration) ||
        ts.isInterfaceDeclaration(declaration)
      ) {
        for (const heritageClause of declaration.heritageClauses || []) {
          for (const type of heritageClause.types || []) {
            if (ts.isIdentifier(type.expression)) {
              const subclassName = type.expression.escapedText;
              if (subclassName === localSymbolName) {
                // TODO: We may need to change this to return a Union type if
                // more than one public type corresponds to the private type.
                return symbol;
              }
            }
          }
        }
      }
    }
  }

  // If no symbol was found that extends the private symbol, check the reverse.
  // We might find an exported symbol in the inheritance chain of the local
  // symbol. Note that this is not always safe as we might replace the local
  // symbol with a less restrictive type.
  const localSymbol = typeChecker.getSymbolAtLocation(typeName);
  if (localSymbol) {
    for (const declaration of localSymbol!.declarations) {
      if (
        ts.isClassDeclaration(declaration) ||
        ts.isInterfaceDeclaration(declaration)
      ) {
        for (const heritageClause of declaration.heritageClauses || []) {
          for (const type of heritageClause.types || []) {
            if (ts.isIdentifier(type.expression)) {
              if (isExported(typeChecker, sourceFile, type.expression)) {
                return typeChecker.getSymbolAtLocation(type.expression);
              }
            }
          }
        }
      }
    }
  }

  return undefined;
}